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

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