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

Side by Side 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: 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library object_graph; 5 library object_graph;
6 6
7 import 'dart:async';
7 import 'dart:typed_data'; 8 import 'dart:typed_data';
8 9 import 'dart:collection';
9 import 'dominator_tree.dart';
10 10
11 // Port of dart::ReadStream from vm/datastream.h. 11 // Port of dart::ReadStream from vm/datastream.h.
12 class ReadStream { 12 class _ReadStream {
13 int _cur = 0; 13 int position = 0;
14 final ByteData _data; 14 int _size = 0;
15 15 final List<ByteData> _chunks;
16 ReadStream(this._data); 16
17 17 _ReadStream(this._chunks) {
18 int get pendingBytes => _data.lengthInBytes - _cur; 18 int n = _chunks.length;
19 19 for (var i = 0; i < n; i++) {
20 var chunk = _chunks[i];
21 if (i + 1 != n) {
22 assert(chunk.lengthInBytes == (1 << 20));
23 }
24 _size += chunk.lengthInBytes;
25 }
26 }
27
28 int get pendingBytes => _size - position;
29
30 int getUint8(i) {
31 return _chunks[i >> 20].getUint8(i & 0xFFFFF);
32 }
33
20 int readUnsigned() { 34 int readUnsigned() {
21 int result = 0; 35 int result = 0;
22 int shift = 0; 36 int shift = 0;
23 while (_data.getUint8(_cur) <= maxUnsignedDataPerByte) { 37 while (getUint8(position) <= maxUnsignedDataPerByte) {
24 result |= _data.getUint8(_cur) << shift; 38 result |= getUint8(position) << shift;
25 shift += dataBitsPerByte; 39 shift += dataBitsPerByte;
26 ++_cur; 40 ++position;
27 } 41 }
28 result |= (_data.getUint8(_cur) & byteMask) << shift; 42 result |= (getUint8(position) & byteMask) << shift;
29 ++_cur; 43 ++position;
30 return result; 44 return result;
31 } 45 }
32 46
33 static const int dataBitsPerByte = 7; 47 static const int dataBitsPerByte = 7;
34 static const int byteMask = (1 << dataBitsPerByte) - 1; 48 static const int byteMask = (1 << dataBitsPerByte) - 1;
35 static const int maxUnsignedDataPerByte = byteMask; 49 static const int maxUnsignedDataPerByte = byteMask;
36 } 50 }
37 51
38 class ObjectVertex { 52 class ObjectVertex {
39 // Never null. The isolate root has id 0.
40 final int _id; 53 final int _id;
koda 2015/05/19 20:12:35 The root now has id 1?
rmacnak 2015/05/19 22:16:07 Yes, documented below by the arrays this indexes i
41 bool get isRoot => _id == 0; 54 final ObjectGraph _graph;
42 // TODO(koda): Include units in object graph metadata. 55
43 int addressForWordSize(int bytesPerWord) => _id * 2 * bytesPerWord; 56 ObjectVertex._(this._id, this._graph);
44 // null for VM-heap objects. 57
45 int _shallowSize; 58 bool operator ==(other) => _id == other._id && _graph == other._graph;
46 int get shallowSize => _shallowSize; 59 int get hashCode => _id;
47 int _retainedSize; 60
48 int get retainedSize => _retainedSize; 61 int get retainedSize => _graph._retainedSizes[_id];
49 // null for VM-heap objects. 62 ObjectVertex get dominator => new ObjectVertex._(_graph._doms[_id], _graph);
50 int _classId; 63
51 int get classId => _classId; 64 int get shallowSize {
52 final List<ObjectVertex> succ = new List<ObjectVertex>(); 65 var stream = new _ReadStream(_graph._chunks);
53 ObjectVertex(this._id) : _retainedSize = 0; 66 stream.position = _graph._positions[_id];
54 String toString() => '$_id,$_shallowSize,$succ'; 67 stream.readUnsigned(); // addr
68 return stream.readUnsigned(); // shallowSize
69 }
70
71 int get vmCid {
72 var stream = new _ReadStream(_graph._chunks);
73 stream.position = _graph._positions[_id];
74 stream.readUnsigned(); // addr
75 stream.readUnsigned(); // shallowSize
76 return stream.readUnsigned(); // cid
77 }
78
79 get successors => new _SuccessorsIterable(_graph, _id);
80
81 int get address {
82 // Note that everywhere else in this file, "address" really means an address
83 // scaled down by kObjectAlignment. They were scaled down so they would fit
84 // into Smis on client.
85 var stream = new _ReadStream(_graph._chunks);
86 stream.position = _graph._positions[_id];
87 var scaledAddr = stream.readUnsigned();
88 return scaledAddr * _graph._kObjectAlignment;
89 }
90
91 List<ObjectVertex> get dominatorTreeChildren {
koda 2015/05/19 20:12:35 Is this really fast/simple enough to be a getter?
rmacnak 2015/05/19 22:16:07 No, this is O(2 million) for a dart2js heap.
92 var N = _graph._N;
93 var doms = _graph._doms;
94
95 var parentId = _id;
96 var domChildren = [];
97
98 for (var childId = 1; childId <= N; childId++) {
99 if (doms[childId] == parentId) {
100 domChildren.add(new ObjectVertex._(childId, _graph));
101 }
102 }
103
104 return domChildren;
105 }
55 } 106 }
56 107
57 // See implementation of ObjectGraph::Serialize for format. 108 class _SuccessorsIterable extends IterableBase<ObjectVertex> {
58 class ObjectGraph { 109 final ObjectGraph _graph;
59 final Map<int, ObjectVertex> _idToVertex = new Map<int, ObjectVertex>(); 110 final int _id;
60 111
61 ObjectVertex _asVertex(int id) { 112 _SuccessorsIterable(this._graph, this._id);
62 return _idToVertex.putIfAbsent(id, () => new ObjectVertex(id)); 113
114 Iterator<ObjectVertex> get iterator => new _SuccessorsIterator(_graph, _id);
115 }
116
117 class _SuccessorsIterator implements Iterator<ObjectVertex> {
118 final ObjectGraph _graph;
119 _ReadStream _stream;
120
121 ObjectVertex current;
122
123 _SuccessorsIterator(this._graph, int id) {
124 _stream = new _ReadStream(this._graph._chunks);
125 _stream.position = _graph._positions[id];
126 _stream.readUnsigned(); // addr
127 _stream.readUnsigned(); // shallowSize
128 _stream.readUnsigned(); // cid
63 } 129 }
64 130
65 void _addFrom(ReadStream stream) { 131 bool moveNext() {
66 ObjectVertex obj = _asVertex(stream.readUnsigned()); 132 while (true) {
67 obj._shallowSize = stream.readUnsigned(); 133 var nextAddr = _stream.readUnsigned();
68 obj._classId = stream.readUnsigned(); 134 if (nextAddr == 0) return false;
69 int last = stream.readUnsigned(); 135 var nextId = _graph._addrToId[nextAddr];
70 while (last != 0) { 136 if (nextId == null) continue;
71 obj.succ.add(_asVertex(last)); 137 current = new ObjectVertex._(nextId, _graph);
72 last = stream.readUnsigned(); 138 return true;
73 } 139 }
74 } 140 }
141 }
75 142
76 ObjectGraph(ReadStream reader) { 143 class _VerticesIterable extends IterableBase<ObjectVertex> {
77 while (reader.pendingBytes > 0) { 144 final ObjectGraph _graph;
78 _addFrom(reader); 145
79 } 146 _VerticesIterable(this._graph);
80 _computeRetainedSizes(); 147
81 _mostRetained = new List<ObjectVertex>.from( 148 Iterator<ObjectVertex> get iterator => new _VerticesIterator(_graph);
82 vertices.where((u) => !u.isRoot)); 149 }
150
151 class _VerticesIterator implements Iterator<ObjectVertex> {
152 final ObjectGraph _graph;
153
154 int _nextId = 0;
155 ObjectVertex current;
156
157 _VerticesIterator(this._graph);
158
159 bool moveNext() {
160 if (_nextId == _graph._N) return false;
161 current = new ObjectVertex._(_nextId++, _graph);
162 return true;
163 }
164 }
165
166 class ObjectGraph {
167 ObjectGraph(List<ByteData> chunks, int nodeCount)
168 : this._chunks = chunks
169 , this._N = nodeCount;
170
171 int get size => _size;
172 int get vertexCount => _N;
173 int get edgeCount => _E;
174
175 ObjectVertex get root => new ObjectVertex._(1, this);
176 Iterable<ObjectVertex> get vertices => new _VerticesIterable(this);
177
178 Iterable<ObjectVertex> getMostRetained({int classId, int limit}) {
179 List<ObjectVertex> _mostRetained =
180 new List<ObjectVertex>.from(vertices.where((u) => !u.isRoot));
83 _mostRetained.sort((u, v) => v.retainedSize - u.retainedSize); 181 _mostRetained.sort((u, v) => v.retainedSize - u.retainedSize);
84 }
85 182
86 Iterable<ObjectVertex> get vertices => _idToVertex.values;
87 List<ObjectVertex> _mostRetained;
88
89 ObjectVertex get root => _asVertex(0);
90
91 Iterable<ObjectVertex> getMostRetained({int classId, int limit}) {
92 var result = _mostRetained; 183 var result = _mostRetained;
93 if (classId != null) { 184 if (classId != null) {
94 result = result.where((u) => u.classId == classId); 185 result = result.where((u) => u.classId == classId);
95 } 186 }
96 if (limit != null) { 187 if (limit != null) {
97 result = result.take(limit); 188 result = result.take(limit);
98 } 189 }
99 return result; 190 return result;
100 } 191 }
101 192
102 void _computeRetainedSizes() { 193 Future process(statusReporter) async {
103 // The retained size for an object is the sum of the shallow sizes of 194 // We build futures here instead of marking the steps as async to avoid the
104 // all its descendants in the dominator tree (including itself). 195 // heavy lifting being inside a tranformed method.
105 var d = new Dominator(); 196
106 for (ObjectVertex u in vertices) { 197 statusReporter.add("Finding node positions...");
107 if (u.shallowSize != null) { 198 await new Future(() => _buildPositions());
108 u._retainedSize = u.shallowSize; 199
109 d.addEdges(u, u.succ.where((ObjectVertex v) => v.shallowSize != null)); 200 statusReporter.add("Finding post order...");
110 } 201 await new Future(() => _buildPostOrder());
111 } 202
112 d.computeDominatorTree(root); 203 statusReporter.add("Finding predecessors...");
113 // Compute all retained sizes "bottom up", starting from the leaves. 204 await new Future(() => _buildPredecessors());
114 // Keep track of number of remaining children of each vertex. 205
115 var degree = new Map<ObjectVertex, int>(); 206 statusReporter.add("Finding dominators...");
116 for (ObjectVertex u in vertices) { 207 await new Future(() => _buildDominators());
117 var v = d.dominator(u); 208
118 if (v != null) { 209 _firstPreds = null;
119 degree[v] = 1 + degree.putIfAbsent(v, () => 0); 210 _preds = null;
120 } 211 _postOrderIndex = null;
121 } 212
122 var leaves = new List<ObjectVertex>(); 213 statusReporter.add("Finding retained sizes...");
123 for (ObjectVertex u in vertices) { 214 await new Future(() => _calculateRetainedSizes());
124 if (!degree.containsKey(u)) { 215
125 leaves.add(u); 216 _postOrder = null;
126 } 217
127 } 218 statusReporter.add("Done");
128 while (!leaves.isEmpty) { 219 return this;
129 var v = leaves.removeLast(); 220 }
130 var u = d.dominator(v); 221
131 if (u == null) continue; 222 final List<ByteData> _chunks;
132 u._retainedSize += v._retainedSize; 223
133 if (--degree[u] == 0) { 224 int _kObjectAlignment;
134 leaves.add(u); 225 int _N;
135 } 226 int _E;
136 } 227 int _size;
137 } 228
138 } 229 Map<int, int> _addrToId = new Map<int, int>();
230
231 // Indexed by node id, with id 0 representing invalid/uninitialized.
232 Uint32List _positions; // Position of the node in the snapshot.
233 Uint32List _postOrder; // post-order index -> id
234 Uint32List _postOrderIndex; // id -> post-order index
235 Uint32List _firstPreds; // Offset into preds.
236 Uint32List _preds;
237 Uint32List _doms;
238 Uint32List _retainedSizes;
239
240 void _buildPositions() {
241 var N = _N;
242 var addrToId = _addrToId;
243
244 var positions = new Uint32List(N + 1);
245
246 var stream = new _ReadStream(_chunks);
247 _kObjectAlignment = stream.readUnsigned();
248
249 var id = 1;
250 while (stream.pendingBytes > 0) {
251 positions[id] = stream.position;
252 var addr = stream.readUnsigned();
253 var shallowSize = stream.readUnsigned();
254 var cid = stream.readUnsigned();
255 addrToId[addr] = id;
256
257 var succAddr = stream.readUnsigned();
258 while (succAddr != 0) {
259 succAddr = stream.readUnsigned();
260 }
261 id++;
262 }
263 assert(id == (N + 1));
264
265 var root = addrToId[0];
266 assert(root == 1);
267
268 _positions = positions;
269 }
270
271 void _buildPostOrder() {
272 var N = _N;
273 var E = 0;
274 var addrToId = _addrToId;
275 var positions = _positions;
276
277 var postOrder = new Uint32List(N);
278 var postOrderIndexx = new Uint32List(N + 1);
koda 2015/05/19 20:12:35 xx?
rmacnak 2015/05/19 22:16:07 Renamed to postOrderIndices.
279 var stackNodes = new Uint32List(N);
280 var stackCurrentEdgePos = new Uint32List(N);
281
282 var visited = new Uint8List(N + 1);
283 var postOrderIndex = 0;
284 var stackTop = 0;
285 var root = 1;
286
287 stackNodes[0] = root;
288
289 var stream = new _ReadStream(_chunks);
290 stream.position = positions[root];
291 stream.readUnsigned(); // addr
292 stream.readUnsigned(); // shallowSize
293 stream.readUnsigned(); // cid
294 stackCurrentEdgePos[0] = stream.position;
295 visited[root] = 1;
296
297 while (stackTop >= 0) {
298 var n = stackNodes[stackTop];
299 var edgePos = stackCurrentEdgePos[stackTop];
300
301 stream.position = edgePos;
302 var childAddr = stream.readUnsigned();
303 if (childAddr != 0) {
304 stackCurrentEdgePos[stackTop] = stream.position;
305 var childId = addrToId[childAddr];
306 if (childId == null) continue;
307 ++E;
308 if (visited[childId] == 1) continue;
309
310 ++stackTop;
311 stackNodes[stackTop] = childId;
312
313 stream.position = positions[childId];
314 stream.readUnsigned(); // addr
315 stream.readUnsigned(); // shallowSize
316 stream.readUnsigned(); // cid
317 stackCurrentEdgePos[stackTop] = stream.position; // i.e., first edge
318 visited[childId] = 1;
319 } else {
320 // Done with all children.
321 postOrderIndexx[n] = postOrderIndex;
322 postOrder[postOrderIndex++] = n;
323 --stackTop;
324 }
325 }
326
327 assert(postOrderIndex == N);
328 assert(postOrder[N - 1] == root);
329
330 _postOrder = postOrder;
331 _postOrderIndex = postOrderIndexx;
332 _E = E;
333 }
334
335 void _buildPredecessors() {
336 var N = _N;
337 var E = _E;
338 var addrToId = _addrToId;
339 var positions = _positions;
340
341 var numPreds = new Uint32List(N + 2);
koda 2015/05/19 20:12:35 Explain the +2 (space for sentinel(s)?)
rmacnak 2015/05/19 22:16:07 Added comment. +1 for 0 sentinel and +1 for derivi
342 var preds = new Uint32List(E);
343
344 // Count predecessors of each node.
345 var stream = new _ReadStream(_chunks);
346 for (var i = 1; i <= N; i++) {
347 stream.position = positions[i];
348 stream.readUnsigned(); // addr
349 stream.readUnsigned(); // shallowSize
350 stream.readUnsigned(); // cid
351 var succAddr = stream.readUnsigned();
352 while (succAddr != 0) {
353 var succId = addrToId[succAddr];
354 if (succId != null) {
355 numPreds[succId]++;
356 }
357 succAddr = stream.readUnsigned();
358 }
359 }
360
361 // Assign indices into predecessors array.
362 var firstPreds = numPreds; // Alias.
363 var nextPreds = new Uint32List(N + 1);
364 var predIndex = 0;
365 for (var i = 1; i <= N; i++) {
366 var thisPredIndex = predIndex;
367 predIndex += numPreds[i];
368 firstPreds[i] = thisPredIndex;
369 nextPreds[i] = thisPredIndex;
370 }
371 assert(predIndex == E);
372 firstPreds[N + 1] = E; // Extra entry for cheap boundry detection.
373
374 // Fill predecessors array.
375 for (var i = 1; i <= N; i++) {
376 stream.position = positions[i];
377 stream.readUnsigned(); // addr
378 stream.readUnsigned(); // shallowSize
379 stream.readUnsigned(); // cid
380 var succAddr = stream.readUnsigned();
381 while (succAddr != 0) {
382 var succId = addrToId[succAddr];
383 if (succId != null) {
384 var predIndex = nextPreds[succId]++;
385 preds[predIndex] = i;
386 }
387 succAddr = stream.readUnsigned();
388 }
389 }
390
391 _firstPreds = firstPreds;
392 _preds = preds;
393 }
394
395 // "A Simple, Fast Dominance Algorithm"
396 // Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
397 void _buildDominators() {
398 var N = _N;
399 var E = _E;
400 var addrToId = _addrToId;
401 var postOrder = _postOrder;
402 var postOrderIndex = _postOrderIndex;
403 var firstPreds = _firstPreds;
404 var preds = _preds;
405
406 var root = 1;
407 var rootPostOrderIndex = postOrderIndex[root];
408 var domByPOI = new Uint32List(N + 1);
409
410 domByPOI[rootPostOrderIndex] = rootPostOrderIndex;
411
412 var round = 0;
413 var changed = true;
414 while (changed) {
415 changed = false;
416 print("round $round");
koda 2015/05/19 20:12:35 Remove or change to debug Log with default off.
rmacnak 2015/05/19 22:16:08 Done.
417 round++;
418
419 // Visit the nodes, except the root, in reverse post order (top down).
420 for (var curPostOrderIndex = rootPostOrderIndex - 1;
421 curPostOrderIndex > 1;
422 --curPostOrderIndex) {
423 if (domByPOI[curPostOrderIndex] == rootPostOrderIndex)
424 continue;
425
426 var nodeOrdinal = postOrder[curPostOrderIndex];
427 var newDomIndex = 0; // 0 = undefined
428
429 // Intersect the DOM sets of the node's precedessors.
430 var beginPredIndex = firstPreds[nodeOrdinal];
431 var endPredIndex = firstPreds[nodeOrdinal + 1];
432 for (var predIndex = beginPredIndex;
433 predIndex < endPredIndex;
434 predIndex++) {
435 var predOrdinal = preds[predIndex];
436 var predPostOrderIndex = postOrderIndex[predOrdinal];
437 if (domByPOI[predPostOrderIndex] != 0) {
438 if (newDomIndex == 0) {
439 newDomIndex = predPostOrderIndex;
440 } else {
441 // Note this two finger algorithm to find the DOM intersection
442 // relies on comparing nodes by their post order index.
443 while (predPostOrderIndex != newDomIndex) {
444 while(predPostOrderIndex < newDomIndex)
445 predPostOrderIndex = domByPOI[predPostOrderIndex];
446 while (newDomIndex < predPostOrderIndex)
447 newDomIndex = domByPOI[newDomIndex];
448 }
449 }
450 if (newDomIndex == rootPostOrderIndex) {
451 break;
452 }
453 }
454 }
455 if (newDomIndex != 0 && domByPOI[curPostOrderIndex] != newDomIndex) {
456 domByPOI[curPostOrderIndex] = newDomIndex;
457 changed = true;
458 }
459 }
460 }
461
462 // Reindex doms by id instead of post order index so we can throw away
463 // the post order arrays.
464 var domById = new Uint32List(N + 1);
465 for (var id = 1; id <= N; id++) {
466 domById[id] = postOrder[domByPOI[postOrderIndex[id]]];
467 }
468
469 domById[root] = 0;
470
471 _doms = domById;
472 }
473
474 void _calculateRetainedSizes() {
475 final N = _N;
476 final E = _E;
477
478 var size = 0;
479 var positions = _positions;
480 var postOrder = _postOrder;
481 var doms = _doms;
482 var retainedSizes = new Uint32List(N + 1);
483
484 // Start with retained size as shallow size.
485 var reader = new _ReadStream(_chunks);
486 for (var i = 1; i <= N; i++) {
487 reader.position = positions[i];
488 reader.readUnsigned(); // addr
489 var shallowSize = reader.readUnsigned();
490 retainedSizes[i] = shallowSize;
491 size += shallowSize;
492 }
493
494 // In post order (bottom up), add retained size to dominator's retained
495 // size, skipping root.
496 for (var o = 0; o < (N - 1); o++) {
497 var i = postOrder[o];
498 assert(i != 1);
499 retainedSizes[doms[i]] += retainedSizes[i];
500 }
501
502 _retainedSizes = retainedSizes;
503 _size = size;
504 }
505 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698