| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 library heap_snapshot_element; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:html'; |
| 9 import 'observatory_element.dart'; |
| 10 import 'heap_profile.dart'; |
| 11 import 'package:observatory/app.dart'; |
| 12 import 'package:observatory/service.dart'; |
| 13 import 'package:observatory/elements.dart'; |
| 14 import 'package:observatory/object_graph.dart'; |
| 15 import 'package:logging/logging.dart'; |
| 16 import 'package:polymer/polymer.dart'; |
| 17 |
| 18 class DominatorTreeRow extends TableTreeRow { |
| 19 final ObjectVertex vertex; |
| 20 final HeapSnapshot snapshot; |
| 21 |
| 22 var _domTreeChildren; |
| 23 get domTreeChildren { |
| 24 if (_domTreeChildren == null) { |
| 25 _domTreeChildren = vertex.dominatorTreeChildren(); |
| 26 } |
| 27 return _domTreeChildren; |
| 28 } |
| 29 |
| 30 DominatorTreeRow(TableTree tree, |
| 31 TableTreeRow parent, |
| 32 this.vertex, |
| 33 this.snapshot) |
| 34 : super(tree, parent) { |
| 35 } |
| 36 |
| 37 bool hasChildren() { |
| 38 return domTreeChildren.length > 0; |
| 39 } |
| 40 |
| 41 static const int kMaxChildren = 100; |
| 42 static const int kMinRetainedSize = 4096; |
| 43 |
| 44 void onShow() { |
| 45 super.onShow(); |
| 46 if (children.length == 0) { |
| 47 domTreeChildren.sort((a, b) => b.retainedSize - a.retainedSize); |
| 48 int includedChildren = 0; |
| 49 for (var childVertex in domTreeChildren) { |
| 50 if (childVertex.retainedSize >= kMinRetainedSize) { |
| 51 if (++includedChildren <= kMaxChildren) { |
| 52 var row = new DominatorTreeRow(tree, this, childVertex, snapshot); |
| 53 children.add(row); |
| 54 } |
| 55 } |
| 56 } |
| 57 } |
| 58 |
| 59 var firstColumn = flexColumns[0]; |
| 60 firstColumn.style.justifyContent = 'flex-start'; |
| 61 firstColumn.style.position = 'relative'; |
| 62 firstColumn.style.alignItems = 'center'; |
| 63 firstColumn.style.setProperty('overflow-x', 'hidden'); |
| 64 |
| 65 var percentRetained = vertex.retainedSize / snapshot.graph.size; |
| 66 var percentNode = new SpanElement(); |
| 67 percentNode.text = Utils.formatPercentNormalized(percentRetained); |
| 68 percentNode.style.minWidth = '5em'; |
| 69 percentNode.style.textAlign = 'right'; |
| 70 percentNode.title = "Retaining x of y."; |
| 71 percentNode.style.display = 'inline-block'; |
| 72 firstColumn.children.add(percentNode); |
| 73 |
| 74 var gap = new SpanElement(); |
| 75 gap.style.minWidth = '1em'; |
| 76 gap.style.display = 'inline-block'; |
| 77 firstColumn.children.add(gap); |
| 78 |
| 79 AnyServiceRefElement objectRef = new Element.tag("any-service-ref"); |
| 80 String hexAddress = vertex.address.toRadixString(16); |
| 81 snapshot.isolate.getObjectByAddress(hexAddress).then((obj) { |
| 82 objectRef.ref = obj; |
| 83 }); |
| 84 objectRef.style.alignSelf = 'center'; |
| 85 firstColumn.children.add(objectRef); |
| 86 |
| 87 var secondColumn = flexColumns[1]; |
| 88 secondColumn.style.justifyContent = 'flex-end'; |
| 89 secondColumn.style.position = 'relative'; |
| 90 secondColumn.style.alignItems = 'center'; |
| 91 secondColumn.style.paddingRight = '0.5em'; |
| 92 secondColumn.text = Utils.formatSize(vertex.retainedSize); |
| 93 } |
| 94 } |
| 95 |
| 96 |
| 97 class MergedVerticesRow extends TableTreeRow { |
| 98 final Isolate isolate; |
| 99 final List<MergedVertex> mergedVertices; |
| 100 |
| 101 MergedVerticesRow(TableTree tree, |
| 102 TableTreeRow parent, |
| 103 this.isolate, |
| 104 this.mergedVertices) |
| 105 : super(tree, parent) { |
| 106 } |
| 107 |
| 108 bool hasChildren() { |
| 109 return mergedVertices.length > 0; |
| 110 } |
| 111 |
| 112 void onShow() { |
| 113 super.onShow(); |
| 114 |
| 115 if (children.length == 0) { |
| 116 mergedVertices.sort((a, b) => b.shallowSize - a.shallowSize); |
| 117 for (var mergedVertex in mergedVertices) { |
| 118 if (mergedVertex.instances > 0) { |
| 119 var row = new MergedVertexRow(tree, this, isolate, mergedVertex); |
| 120 children.add(row); |
| 121 } |
| 122 } |
| 123 } |
| 124 } |
| 125 } |
| 126 |
| 127 class MergedVertexRow extends TableTreeRow { |
| 128 final Isolate isolate; |
| 129 final MergedVertex vertex; |
| 130 |
| 131 MergedVertexRow(TableTree tree, |
| 132 TableTreeRow parent, |
| 133 this.isolate, |
| 134 this.vertex) |
| 135 : super(tree, parent) { |
| 136 } |
| 137 |
| 138 bool hasChildren() { |
| 139 return vertex.outgoingEdges.length > 0 || |
| 140 vertex.incomingEdges.length > 0; |
| 141 } |
| 142 |
| 143 void onShow() { |
| 144 super.onShow(); |
| 145 if (children.length == 0) { |
| 146 children.add(new MergedEdgesRow(tree, this, isolate, vertex, true)); |
| 147 children.add(new MergedEdgesRow(tree, this, isolate, vertex, false)); |
| 148 } |
| 149 |
| 150 |
| 151 var firstColumn = flexColumns[0]; |
| 152 firstColumn.style.justifyContent = 'flex-start'; |
| 153 firstColumn.style.position = 'relative'; |
| 154 firstColumn.style.alignItems = 'center'; |
| 155 |
| 156 var percentNode = new SpanElement(); |
| 157 percentNode.text = "${vertex.instances} instances of"; |
| 158 percentNode.style.minWidth = '5em'; |
| 159 percentNode.style.textAlign = 'right'; |
| 160 firstColumn.children.add(percentNode); |
| 161 |
| 162 var gap = new SpanElement(); |
| 163 gap.style.minWidth = '1em'; |
| 164 gap.style.display = 'inline-block'; |
| 165 firstColumn.children.add(gap); |
| 166 |
| 167 ClassRefElement classRef = new Element.tag("class-ref"); |
| 168 classRef.ref = isolate.getClassByCid(vertex.cid); |
| 169 classRef.style.alignSelf = 'center'; |
| 170 firstColumn.children.add(classRef); |
| 171 |
| 172 var secondColumn = flexColumns[1]; |
| 173 secondColumn.style.justifyContent = 'flex-end'; |
| 174 secondColumn.style.position = 'relative'; |
| 175 secondColumn.style.alignItems = 'center'; |
| 176 secondColumn.style.paddingRight = '0.5em'; |
| 177 secondColumn.text = Utils.formatSize(vertex.shallowSize); |
| 178 } |
| 179 } |
| 180 |
| 181 class MergedEdgesRow extends TableTreeRow { |
| 182 final Isolate isolate; |
| 183 final MergedVertex vertex; |
| 184 final bool outgoing; |
| 185 |
| 186 MergedEdgesRow(TableTree tree, |
| 187 TableTreeRow parent, |
| 188 this.isolate, |
| 189 this.vertex, |
| 190 this.outgoing) |
| 191 : super(tree, parent) { |
| 192 } |
| 193 |
| 194 bool hasChildren() { |
| 195 return outgoing |
| 196 ? vertex.outgoingEdges.length > 0 |
| 197 : vertex.incomingEdges.length > 0; |
| 198 } |
| 199 |
| 200 void onShow() { |
| 201 super.onShow(); |
| 202 if (children.length == 0) { |
| 203 if (outgoing) { |
| 204 var outgoingEdges = vertex.outgoingEdges.values.toList(); |
| 205 outgoingEdges.sort((a, b) => b.shallowSize - a.shallowSize); |
| 206 for (var edge in outgoingEdges) { |
| 207 if (edge.count > 0) { |
| 208 var row = new MergedEdgeRow(tree, this, isolate, edge, true); |
| 209 children.add(row); |
| 210 } |
| 211 } |
| 212 } else { |
| 213 vertex.incomingEdges.sort((a, b) => b.shallowSize - a.shallowSize); |
| 214 for (var edge in vertex.incomingEdges) { |
| 215 if (edge.count > 0) { |
| 216 var row = new MergedEdgeRow(tree, this, isolate, edge, false); |
| 217 children.add(row); |
| 218 } |
| 219 } |
| 220 } |
| 221 } |
| 222 |
| 223 var count = 0; |
| 224 var shallowSize = 0; |
| 225 var edges = outgoing ? vertex.outgoingEdges.values : vertex.incomingEdges; |
| 226 for (var edge in edges) { |
| 227 count += edge.count; |
| 228 shallowSize += edge.shallowSize; |
| 229 } |
| 230 |
| 231 var firstColumn = flexColumns[0]; |
| 232 firstColumn.style.justifyContent = 'flex-start'; |
| 233 firstColumn.style.position = 'relative'; |
| 234 firstColumn.style.alignItems = 'center'; |
| 235 |
| 236 var countNode = new SpanElement(); |
| 237 countNode.text = "$count"; |
| 238 countNode.style.minWidth = '5em'; |
| 239 countNode.style.textAlign = 'right'; |
| 240 firstColumn.children.add(countNode); |
| 241 |
| 242 var gap = new SpanElement(); |
| 243 gap.style.minWidth = '1em'; |
| 244 gap.style.display = 'inline-block'; |
| 245 firstColumn.children.add(gap); |
| 246 |
| 247 var labelNode = new SpanElement(); |
| 248 labelNode.text = outgoing ? "Outgoing references" : "Incoming references"; |
| 249 firstColumn.children.add(labelNode); |
| 250 |
| 251 var secondColumn = flexColumns[1]; |
| 252 secondColumn.style.justifyContent = 'flex-end'; |
| 253 secondColumn.style.position = 'relative'; |
| 254 secondColumn.style.alignItems = 'center'; |
| 255 secondColumn.style.paddingRight = '0.5em'; |
| 256 secondColumn.text = Utils.formatSize(shallowSize); |
| 257 } |
| 258 } |
| 259 |
| 260 class MergedEdgeRow extends TableTreeRow { |
| 261 final Isolate isolate; |
| 262 final MergedEdge edge; |
| 263 final bool outgoing; |
| 264 |
| 265 MergedEdgeRow(TableTree tree, |
| 266 TableTreeRow parent, |
| 267 this.isolate, |
| 268 this.edge, |
| 269 this.outgoing) |
| 270 : super(tree, parent) { |
| 271 } |
| 272 |
| 273 bool hasChildren() => false; |
| 274 |
| 275 void onShow() { |
| 276 super.onShow(); |
| 277 |
| 278 var firstColumn = flexColumns[0]; |
| 279 firstColumn.style.justifyContent = 'flex-start'; |
| 280 firstColumn.style.position = 'relative'; |
| 281 firstColumn.style.alignItems = 'center'; |
| 282 |
| 283 var percentNode = new SpanElement(); |
| 284 var preposition = outgoing ? "to" : "from"; |
| 285 percentNode.text = "${edge.count} references $preposition instances of"; |
| 286 percentNode.style.minWidth = '5em'; |
| 287 percentNode.style.textAlign = 'right'; |
| 288 firstColumn.children.add(percentNode); |
| 289 |
| 290 var gap = new SpanElement(); |
| 291 gap.style.minWidth = '1em'; |
| 292 gap.style.display = 'inline-block'; |
| 293 firstColumn.children.add(gap); |
| 294 |
| 295 MergedVertex v = outgoing ? edge.target : edge.source; |
| 296 if (v.cid == 0) { |
| 297 var rootName = new SpanElement(); |
| 298 rootName.text = '<root>'; |
| 299 firstColumn.children.add(rootName); |
| 300 } else { |
| 301 ClassRefElement classRef = new Element.tag("class-ref"); |
| 302 classRef.ref = isolate.getClassByCid(v.cid); |
| 303 classRef.style.alignSelf = 'center'; |
| 304 firstColumn.children.add(classRef); |
| 305 } |
| 306 |
| 307 var secondColumn = flexColumns[1]; |
| 308 secondColumn.style.justifyContent = 'flex-end'; |
| 309 secondColumn.style.position = 'relative'; |
| 310 secondColumn.style.alignItems = 'center'; |
| 311 secondColumn.style.paddingRight = '0.5em'; |
| 312 secondColumn.text = Utils.formatSize(edge.shallowSize); |
| 313 } |
| 314 } |
| 315 |
| 316 |
| 317 class MergedEdge { |
| 318 final MergedVertex source; |
| 319 final MergedVertex target; |
| 320 int count = 0; |
| 321 int shallowSize = 0; |
| 322 int retainedSize = 0; |
| 323 |
| 324 MergedEdge(this.source, this.target); |
| 325 } |
| 326 |
| 327 class MergedVertex { |
| 328 final int cid; |
| 329 int instances = 0; |
| 330 int shallowSize = 0; |
| 331 int retainedSize = 0; |
| 332 |
| 333 List<MergedEdge> incomingEdges = new List<MergedEdge>(); |
| 334 Map<int, MergedEdge> outgoingEdges = new Map<int, MergedEdge>(); |
| 335 |
| 336 MergedVertex(this.cid); |
| 337 } |
| 338 |
| 339 |
| 340 Future<List<MergedVertex>> buildMergedVertices(ObjectGraph graph) async { |
| 341 var cidToMergedVertex = {}; |
| 342 |
| 343 for (var vertex in graph.vertices) { |
| 344 var cid = vertex.vmCid; |
| 345 MergedVertex source = cidToMergedVertex[cid]; |
| 346 if (source == null) { |
| 347 cidToMergedVertex[cid] = source = new MergedVertex(cid); |
| 348 } |
| 349 |
| 350 source.instances++; |
| 351 source.shallowSize += (vertex.shallowSize == null ? 0 : vertex.shallowSize); |
| 352 |
| 353 for (var vertex2 in vertex.successors) { |
| 354 var cid2 = vertex2.vmCid; |
| 355 MergedEdge edge = source.outgoingEdges[cid2]; |
| 356 if (edge == null) { |
| 357 MergedVertex target = cidToMergedVertex[cid2]; |
| 358 if (target == null) { |
| 359 cidToMergedVertex[cid2] = target = new MergedVertex(cid2); |
| 360 } |
| 361 edge = new MergedEdge(source, target); |
| 362 source.outgoingEdges[cid2] = edge; |
| 363 target.incomingEdges.add(edge); |
| 364 } |
| 365 edge.count++; |
| 366 // An over-estimate if there are multiple references to the same object. |
| 367 edge.shallowSize += vertex2.shallowSize == null ? 0 : vertex2.shallowSize; |
| 368 } |
| 369 } |
| 370 |
| 371 return cidToMergedVertex.values.toList(); |
| 372 } |
| 373 |
| 374 @CustomTag('heap-snapshot') |
| 375 class HeapSnapshotElement extends ObservatoryElement { |
| 376 @published Isolate isolate; |
| 377 @observable HeapSnapshot snapshot; |
| 378 |
| 379 @published String state = 'Requested'; |
| 380 @published String analysisSelector = 'DominatorTree'; |
| 381 |
| 382 HeapSnapshotElement.created() : super.created(); |
| 383 |
| 384 void analysisSelectorChanged(oldValue) { |
| 385 _update(); |
| 386 } |
| 387 |
| 388 void isolateChanged(oldValue) { |
| 389 if (isolate == null) return; |
| 390 |
| 391 if (isolate.latestSnapshot == null) { |
| 392 _getHeapSnapshot(); |
| 393 } else { |
| 394 snapshot = isolate.latestSnapshot; |
| 395 state = 'Loaded'; |
| 396 _update(); |
| 397 } |
| 398 } |
| 399 |
| 400 Future refresh() { |
| 401 return _getHeapSnapshot(); |
| 402 } |
| 403 |
| 404 Future _getHeapSnapshot() { |
| 405 var completer = new Completer(); |
| 406 state = "Requesting heap snapshot..."; |
| 407 isolate.getClassRefs(); |
| 408 var stopwatch = new Stopwatch()..start(); |
| 409 isolate.fetchHeapSnapshot().listen((event) { |
| 410 if (event is String) { |
| 411 print("${stopwatch.elapsedMilliseconds} $event"); |
| 412 state = event; |
| 413 } else if (event is HeapSnapshot) { |
| 414 snapshot = event; |
| 415 state = 'Loaded'; |
| 416 completer.complete(snapshot); |
| 417 _update(); |
| 418 } else { |
| 419 throw "Unexpected event $event"; |
| 420 } |
| 421 }); |
| 422 return completer.future; |
| 423 } |
| 424 |
| 425 void _update() { |
| 426 if (snapshot == null) { |
| 427 return; |
| 428 } |
| 429 |
| 430 switch(analysisSelector) { |
| 431 case 'DominatorTree': |
| 432 _buildDominatorTree(); |
| 433 break; |
| 434 case 'MergeByClass': |
| 435 _buildMergedVertices(); |
| 436 break; |
| 437 } |
| 438 } |
| 439 |
| 440 void _buildDominatorTree() { |
| 441 var tableBody = shadowRoot.querySelector('#treeBody'); |
| 442 var tree = new TableTree(tableBody, 2); |
| 443 var rootRow = |
| 444 new DominatorTreeRow(tree, null, snapshot.graph.root, snapshot); |
| 445 tree.initialize(rootRow); |
| 446 return; |
| 447 } |
| 448 |
| 449 void _buildMergedVertices() { |
| 450 state = 'Grouping...'; |
| 451 var tableBody = shadowRoot.querySelector('#treeBody'); |
| 452 var tree = new TableTree(tableBody, 2); |
| 453 tableBody.children.clear(); |
| 454 |
| 455 new Future.delayed(const Duration(milliseconds: 500), () { |
| 456 buildMergedVertices(snapshot.graph).then((vertices) { |
| 457 state = 'Loaded'; |
| 458 var rootRow = new MergedVerticesRow(tree, null, isolate, vertices); |
| 459 tree.initialize(rootRow); |
| 460 }); |
| 461 }); |
| 462 } |
| 463 } |
| OLD | NEW |