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

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