Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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 heap_snapshot_element; | 5 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 6 // for details. All rights reserved. Use of this source code is governed by a | |
| 7 // BSD-style license that can be found in the LICENSE file. | |
| 6 | 8 |
| 7 import 'dart:async'; | 9 import 'dart:async'; |
| 8 import 'dart:html'; | 10 import 'dart:html'; |
| 9 import 'class_ref_wrapper.dart'; | 11 import 'dart:math' as Math; |
| 10 import 'observatory_element.dart'; | 12 import 'package:observatory/models.dart' as M; |
| 11 import 'package:observatory/app.dart'; | 13 import 'package:observatory/src/elements/class_ref.dart'; |
| 12 import 'package:observatory/service.dart'; | 14 import 'package:observatory/src/elements/containers/virtual_tree.dart'; |
| 13 import 'package:observatory/elements.dart'; | 15 import 'package:observatory/src/elements/helpers/any_ref.dart'; |
| 14 import 'package:observatory/object_graph.dart'; | 16 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| 15 import 'package:polymer/polymer.dart'; | 17 import 'package:observatory/src/elements/helpers/tag.dart'; |
| 16 import 'package:logging/logging.dart'; | 18 import 'package:observatory/src/elements/helpers/uris.dart'; |
| 17 | 19 import 'package:observatory/src/elements/nav/bar.dart'; |
| 18 class DominatorTreeRow extends TableTreeRow { | 20 import 'package:observatory/src/elements/nav/isolate_menu.dart'; |
| 19 final ObjectVertex vertex; | 21 import 'package:observatory/src/elements/nav/menu.dart'; |
| 20 final HeapSnapshot snapshot; | 22 import 'package:observatory/src/elements/nav/notify.dart'; |
| 21 | 23 import 'package:observatory/src/elements/nav/refresh.dart'; |
| 22 var _domTreeChildren; | 24 import 'package:observatory/src/elements/nav/top_menu.dart'; |
| 23 get domTreeChildren { | 25 import 'package:observatory/src/elements/nav/vm_menu.dart'; |
| 24 if (_domTreeChildren == null) { | 26 import 'package:observatory/utils.dart'; |
| 25 _domTreeChildren = vertex.dominatorTreeChildren(); | 27 |
| 26 } | 28 enum HeapSnapshotTreeMode { |
| 27 return _domTreeChildren; | 29 dominatorTree, |
| 28 } | 30 groupByClass |
| 29 | 31 } |
| 30 DominatorTreeRow(TableTree tree, | 32 |
| 31 TableTreeRow parent, | 33 class HeapSnapshotElement extends HtmlElement implements Renderable { |
| 32 this.vertex, | 34 static const tag = const Tag<HeapSnapshotElement>('heap-snapshot', |
| 33 this.snapshot) | 35 dependencies: const [ |
| 34 : super(tree, parent) { | 36 ClassRefElement.tag, |
| 35 } | 37 NavBarElement.tag, |
| 36 | 38 NavTopMenuElement.tag, |
| 37 bool hasChildren() { | 39 NavVMMenuElement.tag, |
| 38 return domTreeChildren.length > 0; | 40 NavIsolateMenuElement.tag, |
| 41 NavMenuElement.tag, | |
| 42 NavRefreshElement.tag, | |
| 43 NavNotifyElement.tag, | |
| 44 VirtualTreeElement.tag, | |
| 45 ]); | |
| 46 | |
| 47 RenderingScheduler<HeapSnapshotElement> _r; | |
| 48 | |
| 49 Stream<RenderedEvent<HeapSnapshotElement>> get onRendered => _r.onRendered; | |
| 50 | |
| 51 M.VM _vm; | |
| 52 M.IsolateRef _isolate; | |
| 53 M.EventRepository _events; | |
| 54 M.NotificationRepository _notifications; | |
| 55 M.HeapSnapshotRepository _snapshots; | |
| 56 M.InstanceRepository _instances; | |
| 57 M.HeapSnapshot _snapshot; | |
| 58 Stream<M.HeapSnapshotLoadingProgressEvent> _progressStream; | |
| 59 M.HeapSnapshotLoadingProgress _progress; | |
| 60 HeapSnapshotTreeMode _mode = HeapSnapshotTreeMode.dominatorTree; | |
| 61 | |
| 62 | |
| 63 M.IsolateRef get isolate => _isolate; | |
| 64 M.NotificationRepository get notifications => _notifications; | |
| 65 M.HeapSnapshotRepository get profiles => _snapshots; | |
| 66 M.VMRef get vm => _vm; | |
| 67 | |
| 68 factory HeapSnapshotElement(M.VM vm, M.IsolateRef isolate, | |
| 69 M.EventRepository events, | |
| 70 M.NotificationRepository notifications, | |
| 71 M.HeapSnapshotRepository snapshots, | |
| 72 M.InstanceRepository instances, | |
| 73 {RenderingQueue queue}) { | |
| 74 assert(vm != null); | |
| 75 assert(isolate != null); | |
| 76 assert(events != null); | |
| 77 assert(notifications != null); | |
| 78 assert(snapshots != null); | |
| 79 assert(instances != null); | |
| 80 HeapSnapshotElement e = document.createElement(tag.name); | |
| 81 e._r = new RenderingScheduler(e, queue: queue); | |
| 82 e._vm = vm; | |
| 83 e._isolate = isolate; | |
| 84 e._events = events; | |
| 85 e._notifications = notifications; | |
| 86 e._snapshots = snapshots; | |
| 87 e._instances = instances; | |
| 88 return e; | |
| 89 } | |
| 90 | |
| 91 HeapSnapshotElement.created() : super.created(); | |
| 92 | |
| 93 @override | |
| 94 attached() { | |
| 95 super.attached(); | |
| 96 _r.enable(); | |
| 97 _refresh(); | |
| 98 } | |
| 99 | |
| 100 @override | |
| 101 detached() { | |
| 102 super.detached(); | |
| 103 _r.disable(notify: true); | |
| 104 children = []; | |
| 105 } | |
| 106 | |
| 107 void render() { | |
| 108 var content = [ | |
| 109 new NavBarElement(queue: _r.queue) | |
| 110 ..children = [ | |
| 111 new NavTopMenuElement(queue: _r.queue), | |
| 112 new NavVMMenuElement(_vm, _events, queue: _r.queue), | |
| 113 new NavIsolateMenuElement(_isolate, _events, queue: _r.queue), | |
| 114 new NavMenuElement('heap snapshot', link: Uris.profiler(_isolate), | |
| 115 last: true, queue: _r.queue), | |
| 116 new NavRefreshElement(queue: _r.queue) | |
| 117 ..disabled = M.isHeapSnapshotProgressRunning(_progress?.status) | |
| 118 ..onRefresh.listen((e) { | |
| 119 _refresh(); | |
| 120 }), | |
| 121 new NavNotifyElement(_notifications, queue: _r.queue) | |
| 122 ], | |
| 123 ]; | |
| 124 if (_progress == null) { | |
| 125 children = content; | |
| 126 return; | |
| 127 } | |
| 128 switch (_progress.status) { | |
|
rmacnak
2016/08/23 16:57:09
Because computing the dominator tree can take quit
cbernaschina
2016/08/23 17:28:28
Done.
| |
| 129 case M.HeapSnapshotLoadingStatus.fetching : | |
| 130 content.addAll(_createStatusMessage('Fetching profile from VM...')); | |
|
rmacnak
2016/08/23 16:57:09
profile -> snapshot
cbernaschina
2016/08/23 17:28:28
Done.
| |
| 131 break; | |
| 132 case M.HeapSnapshotLoadingStatus.loading : | |
| 133 content.addAll(_createStatusMessage('Loading profile...', | |
| 134 progress: _progress.progress)); | |
| 135 break; | |
| 136 case M.HeapSnapshotLoadingStatus.disabled : | |
| 137 content.addAll(_createDisabledMessage()); | |
| 138 break; | |
| 139 case M.HeapSnapshotLoadingStatus.loaded: | |
| 140 content.addAll(_createReport()); | |
| 141 break; | |
| 142 } | |
| 143 children = content; | |
| 144 } | |
| 145 | |
| 146 Future _refresh() async { | |
| 147 _progress = null; | |
| 148 _progressStream = _snapshots.get(isolate); | |
| 149 _r.dirty(); | |
| 150 _progressStream.listen((_) => _r.dirty()); | |
| 151 _progress = (await _progressStream.first).progress; | |
| 152 _r.dirty(); | |
| 153 if (M.isHeapSnapshotProgressRunning(_progress.status)) { | |
| 154 _progress = (await _progressStream.last).progress; | |
| 155 _snapshot = _progress.snapshot; | |
| 156 _r.dirty(); | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 static List<Element> _createStatusMessage(String message, | |
| 161 {double progress: 0.0}) { | |
| 162 return [ | |
| 163 new DivElement()..classes = ['content-centered-big'] | |
| 164 ..children = [ | |
| 165 new DivElement()..classes = ['statusBox', 'shadow', 'center'] | |
| 166 ..children = [ | |
| 167 new DivElement()..classes = ['statusMessage'] | |
| 168 ..text = message, | |
| 169 new DivElement()..style.background = '#0489c3' | |
| 170 ..style.width = '$progress%' | |
| 171 ..style.height = '15px' | |
| 172 ..style.borderRadius = '4px' | |
| 173 ] | |
| 174 ] | |
| 175 ]; | |
| 176 } | |
| 177 | |
| 178 static List<Element> _createDisabledMessage() { | |
|
rmacnak
2016/08/23 16:57:09
The profile flag has no effect on the heap snapsho
cbernaschina
2016/08/23 17:28:28
Done
| |
| 179 return [ | |
| 180 new DivElement()..classes = ['content-centered-big'] | |
| 181 ..children = [ | |
| 182 new DivElement()..classes = ['statusBox' 'shadow' 'center'] | |
| 183 ..children = [ | |
| 184 new DivElement() | |
| 185 ..children = [ | |
| 186 new HeadingElement.h1() | |
| 187 ..text = 'Profiling is disabled', | |
| 188 new BRElement(), | |
| 189 new DivElement() | |
| 190 ..innerHtml = 'Perhaps the <b>profile</b> ' | |
| 191 'flag has been disabled for this VM.', | |
| 192 new BRElement(), | |
| 193 new SpanElement()..text = 'See all', | |
| 194 new AnchorElement(href: Uris.flags())..text = 'vm flags' | |
| 195 ] | |
| 196 ] | |
| 197 ] | |
| 198 ]; | |
| 199 } | |
| 200 | |
| 201 VirtualTreeElement _tree; | |
| 202 | |
| 203 List<Element> _createReport() { | |
| 204 var report = [ | |
| 205 new DivElement()..classes = ['content-centered-big'] | |
| 206 ..children = [ | |
| 207 new DivElement()..classes = ['memberList'] | |
| 208 ..children = [ | |
| 209 new DivElement()..classes = ['memberItem'] | |
| 210 ..children = [ | |
| 211 new DivElement()..classes = ['memberName'] | |
| 212 ..text = 'Refreshed ', | |
| 213 new DivElement()..classes = ['memberName'] | |
| 214 ..text = Utils.formatDateTime(_snapshot.timestamp) | |
| 215 ], | |
| 216 new DivElement()..classes = ['memberItem'] | |
| 217 ..children = [ | |
| 218 new DivElement()..classes = ['memberName'] | |
| 219 ..text = 'Objects ', | |
| 220 new DivElement()..classes = ['memberName'] | |
| 221 ..text = '${_snapshot.objects}' | |
| 222 ], | |
| 223 new DivElement()..classes = ['memberItem'] | |
| 224 ..children = [ | |
| 225 new DivElement()..classes = ['memberName'] | |
| 226 ..text = 'References ', | |
| 227 new DivElement()..classes = ['memberName'] | |
| 228 ..text = '${_snapshot.references}' | |
| 229 ], | |
| 230 new DivElement()..classes = ['memberItem'] | |
| 231 ..children = [ | |
| 232 new DivElement()..classes = ['memberName'] | |
| 233 ..text = 'Size ', | |
| 234 new DivElement()..classes = ['memberName'] | |
| 235 ..text = Utils.formatSize(_snapshot.size) | |
| 236 ], | |
| 237 new DivElement()..classes = ['memberItem'] | |
| 238 ..children = [ | |
| 239 new DivElement()..classes = ['memberName'] | |
| 240 ..text = 'Analysis ', | |
| 241 new DivElement()..classes = ['memberName'] | |
| 242 ..children = _createModeSelect() | |
| 243 ] | |
| 244 ] | |
| 245 ], | |
| 246 ]; | |
| 247 switch (_mode) { | |
| 248 case HeapSnapshotTreeMode.dominatorTree: | |
| 249 _tree = new VirtualTreeElement(_createDominator, _updateDominator, | |
| 250 _getChildrenDominator, | |
| 251 items: _getChildrenDominator(_snapshot.dominatorTree), | |
| 252 queue: _r.queue); | |
| 253 _tree.expand(_snapshot.dominatorTree); | |
| 254 final text = 'In a heap dominator tree, an object X is a parent of ' | |
| 255 'object Y if every path from the root to Y goes through ' | |
| 256 'X. This allows you to find "choke points" that are ' | |
| 257 'holding onto a lot memory. If an object becomes garbage, ' | |
|
rmacnak
2016/08/23 16:57:09
a lot of memory
cbernaschina
2016/08/23 17:28:28
Done.
| |
| 258 'all its children in the dominator tree become garbage as ' | |
| 259 'well. The retained size of an object is the sum of the ' | |
| 260 'retained sizes of its children in the dominator tree ' | |
| 261 'plus its own shallow size, and is the amount of memory ' | |
| 262 'that would be freed if the object became garbage.'; | |
| 263 report.addAll([ | |
| 264 new DivElement()..classes = ['content-centered-big', 'explanation'] | |
| 265 ..text = text | |
| 266 ..title = text, | |
| 267 _tree | |
| 268 ]); | |
| 269 break; | |
| 270 case HeapSnapshotTreeMode.groupByClass: | |
| 271 final items = _snapshot.classReferences.toList(); | |
| 272 items.sort((a, b) => b.shallowSize - a.shallowSize); | |
| 273 _tree = new VirtualTreeElement(_createGroup, _updateGroup, | |
| 274 _getChildrenGroup, items: items, queue: _r.queue); | |
| 275 _tree.expand(_snapshot.dominatorTree); | |
| 276 report.add(_tree); | |
| 277 break; | |
| 278 default: | |
| 279 break; | |
| 280 } | |
| 281 return report; | |
| 282 } | |
| 283 | |
| 284 static Element _createDominator(toggle) { | |
| 285 return new DivElement() | |
| 286 ..classes = const ['tree-item'] | |
| 287 ..children = [ | |
| 288 new SpanElement()..classes = const ['size'] | |
| 289 ..title = 'retained size', | |
| 290 new SpanElement()..classes = const ['lines'], | |
| 291 new ButtonElement()..classes = const ['expander'] | |
| 292 ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), | |
| 293 new SpanElement()..classes = const ['percentage'] | |
| 294 ..title = 'percentage of heap being retained', | |
| 295 new SpanElement()..classes = const ['name'] | |
| 296 ]; | |
| 297 } | |
| 298 | |
| 299 static Element _createGroup(toggle) { | |
| 300 return new DivElement() | |
| 301 ..classes = const ['tree-item'] | |
| 302 ..children = [ | |
| 303 new SpanElement()..classes = const ['size'] | |
| 304 ..title = 'shallow size', | |
| 305 new SpanElement()..classes = const ['lines'], | |
| 306 new ButtonElement()..classes = const ['expander'] | |
| 307 ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), | |
| 308 new SpanElement()..classes = const ['count'] | |
| 309 ..title = 'shallow size', | |
| 310 new SpanElement()..classes = const ['name'] | |
| 311 ]; | |
| 39 } | 312 } |
| 40 | 313 |
| 41 static const int kMaxChildren = 100; | 314 static const int kMaxChildren = 100; |
| 42 static const int kMinRetainedSize = 4096; | 315 static const int kMinRetainedSize = 4096; |
| 43 | 316 |
| 44 void onShow() { | 317 static _getChildrenDominator(M.HeapSnapshotDominatorNode node) { |
| 45 super.onShow(); | 318 final list = node.children.toList(); |
| 46 if (children.length == 0) { | 319 list.sort((a, b) => b.retainedSize - a.retainedSize); |
| 47 domTreeChildren.sort((a, b) => b.retainedSize - a.retainedSize); | 320 return list.where((child) => child.retainedSize >= kMinRetainedSize) |
| 48 int includedChildren = 0; | 321 .take(kMaxChildren); |
| 49 for (var childVertex in domTreeChildren) { | 322 } |
| 50 if (childVertex.retainedSize >= kMinRetainedSize) { | 323 |
| 51 if (++includedChildren <= kMaxChildren) { | 324 static _getChildrenGroup(item) { |
| 52 var row = new DominatorTreeRow(tree, this, childVertex, snapshot); | 325 if (item is M.HeapSnapshotClassReferences) { |
| 53 children.add(row); | 326 if (item.inbounds.isNotEmpty || item.outbounds.isNotEmpty) { |
| 54 } | 327 return [item.inbounds, item.outbounds]; |
| 55 } | |
| 56 } | 328 } |
| 57 } | 329 } else if (item is Iterable) { |
| 58 | 330 return item.toList()..sort((a, b) => b.shallowSize - a.shallowSize); |
| 59 var firstColumn = flexColumns[0]; | 331 } |
| 60 firstColumn.style.justifyContent = 'flex-start'; | 332 return const []; |
| 61 firstColumn.style.position = 'relative'; | 333 } |
| 62 firstColumn.style.alignItems = 'center'; | 334 |
| 63 firstColumn.style.setProperty('overflow-x', 'hidden'); | 335 void _updateDominator(HtmlElement element, M.HeapSnapshotDominatorNode node, |
| 64 | 336 int depth) { |
| 65 var percentRetained = vertex.retainedSize / snapshot.graph.size; | 337 element.children[0].text = Utils.formatSize(node.shallowSize); |
|
rmacnak
2016/08/23 17:01:02
retainedSize
cbernaschina
2016/08/23 17:28:28
Done.
| |
| 66 var percentNode = new SpanElement(); | 338 _updateLines(element.children[1].children, depth); |
| 67 percentNode.text = Utils.formatPercentNormalized(percentRetained); | 339 if (_getChildrenDominator(node).isNotEmpty) { |
| 68 percentNode.style.minWidth = '5em'; | 340 element.children[2].text = _tree.isExpanded(node) ? 'â–¼' : 'â–º'; |
| 69 percentNode.style.textAlign = 'right'; | 341 } else { |
| 70 percentNode.title = "Percent of heap being retained"; | 342 element.children[2].text = ''; |
| 71 percentNode.style.display = 'inline-block'; | 343 } |
| 72 firstColumn.children.add(percentNode); | 344 element.children[3].text = Utils.formatPercentNormalized( |
| 73 | 345 node.retainedSize * 1.0 / _snapshot.size); |
| 74 var gap = new SpanElement(); | 346 final wrapper = new SpanElement()..classes = const ['name'] |
| 75 gap.style.minWidth = '1em'; | 347 ..text = 'Loading...'; |
| 76 gap.style.display = 'inline-block'; | 348 element.children[4] = wrapper; |
| 77 firstColumn.children.add(gap); | 349 node.object.then((object) { |
| 78 | 350 wrapper..text = '' |
| 79 AnyServiceRefElement objectRef = new Element.tag("any-service-ref"); | 351 ..children = [anyRef(_isolate, object, _instances, queue: _r.queue)]; |
| 80 snapshot.isolate.getObjectByAddress(vertex.address).then((obj) { | |
| 81 objectRef.ref = obj; | |
| 82 }); | 352 }); |
| 83 objectRef.style.alignSelf = 'center'; | 353 } |
| 84 firstColumn.children.add(objectRef); | 354 |
| 85 | 355 void _updateGroup(HtmlElement element, item, int depth) { |
| 86 var secondColumn = flexColumns[1]; | 356 _updateLines(element.children[1].children, depth); |
| 87 secondColumn.style.justifyContent = 'flex-end'; | 357 if (item is M.HeapSnapshotClassReferences) { |
| 88 secondColumn.style.position = 'relative'; | 358 element.children[0].text = Utils.formatSize(item.shallowSize); |
| 89 secondColumn.style.alignItems = 'center'; | 359 element.children[2].text = _tree.isExpanded(item) ? 'â–¼' : 'â–º'; |
| 90 secondColumn.style.paddingRight = '0.5em'; | 360 element.children[3].text = '${item.instances} instances of '; |
| 91 secondColumn.text = Utils.formatSize(vertex.retainedSize); | 361 element.children[4] = new ClassRefElement(_isolate, item.clazz, |
| 362 queue: _r.queue)..classes = const ['name']; | |
| 363 } else if (item is Iterable) { | |
| 364 element.children[0].text = ''; | |
| 365 if (item.isNotEmpty) { | |
| 366 element.children[2].text = _tree.isExpanded(item) ? 'â–¼' : 'â–º'; | |
| 367 } else { | |
| 368 element.children[2].text = ''; | |
| 369 } | |
| 370 element.children[3].text = ''; | |
| 371 if (item is Iterable<M.HeapSnapshotClassInbound>) { | |
| 372 element.children[4] = new SpanElement()..classes = const ['name'] | |
| 373 ..text = '${item.length} Ingoing references'; | |
| 374 } else { | |
| 375 element.children[4] = new SpanElement()..classes = const ['name'] | |
| 376 ..text = '${item.length} Outgoing references'; | |
| 377 } | |
| 378 } else { | |
| 379 element.children[0].text = ''; | |
| 380 element.children[2].text = ''; | |
| 381 element.children[3].text = ''; | |
| 382 element.children[4] = new SpanElement()..classes = const ['name']; | |
| 383 if (item is M.HeapSnapshotClassInbound){ | |
| 384 element.children[3].text = | |
| 385 '${item.count} references from instances of '; | |
| 386 element.children[4].children = [ | |
| 387 new ClassRefElement(_isolate, item.source, | |
| 388 queue: _r.queue) | |
| 389 ]; | |
| 390 } else if (item is M.HeapSnapshotClassOutbound){ | |
| 391 element.children[3]..text = '${item.count} references to instances of '; | |
| 392 element.children[4].children = [ | |
| 393 new ClassRefElement(_isolate, item.target, | |
| 394 queue: _r.queue) | |
| 395 ]; | |
| 396 } | |
| 397 } | |
| 398 } | |
| 399 | |
| 400 static _updateLines(List<Element> lines, int n) { | |
| 401 n = Math.max(0, n); | |
| 402 while (lines.length > n) { | |
| 403 lines.removeLast(); | |
| 404 } | |
| 405 while (lines.length < n) { | |
| 406 lines.add(new SpanElement()); | |
| 407 } | |
| 408 } | |
| 409 | |
| 410 static String modeToString(HeapSnapshotTreeMode mode) { | |
| 411 switch (mode) { | |
| 412 case HeapSnapshotTreeMode.dominatorTree: return 'Dominator tree'; | |
| 413 case HeapSnapshotTreeMode.groupByClass: return 'Group by class'; | |
| 414 } | |
| 415 throw new Exception('Unknown ProfileTreeMode'); | |
| 416 } | |
| 417 | |
| 418 List<Element> _createModeSelect() { | |
| 419 var s; | |
| 420 return [ | |
| 421 s = new SelectElement()..classes = ['analysis-select'] | |
| 422 ..value = modeToString(_mode) | |
| 423 ..children = HeapSnapshotTreeMode.values.map((mode) { | |
| 424 return new OptionElement(value: modeToString(mode), | |
| 425 selected: _mode == mode) | |
| 426 ..text = modeToString(mode); | |
| 427 }).toList(growable: false) | |
| 428 ..onChange.listen((_) { | |
| 429 _mode = HeapSnapshotTreeMode.values[s.selectedIndex]; | |
| 430 _r.dirty(); | |
| 431 }) | |
| 432 ]; | |
| 92 } | 433 } |
| 93 } | 434 } |
| 94 | |
| 95 | |
| 96 class MergedVerticesRow extends TableTreeRow { | |
| 97 final Isolate isolate; | |
| 98 final List<MergedVertex> mergedVertices; | |
| 99 | |
| 100 MergedVerticesRow(TableTree tree, | |
| 101 TableTreeRow parent, | |
| 102 this.isolate, | |
| 103 this.mergedVertices) | |
| 104 : super(tree, parent) { | |
| 105 } | |
| 106 | |
| 107 bool hasChildren() { | |
| 108 return mergedVertices.length > 0; | |
| 109 } | |
| 110 | |
| 111 void onShow() { | |
| 112 super.onShow(); | |
| 113 | |
| 114 if (children.length == 0) { | |
| 115 mergedVertices.sort((a, b) => b.shallowSize - a.shallowSize); | |
| 116 for (var mergedVertex in mergedVertices) { | |
| 117 if (mergedVertex.instances > 0) { | |
| 118 var row = new MergedVertexRow(tree, this, isolate, mergedVertex); | |
| 119 children.add(row); | |
| 120 } | |
| 121 } | |
| 122 } | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 class MergedVertexRow extends TableTreeRow { | |
| 127 final Isolate isolate; | |
| 128 final MergedVertex vertex; | |
| 129 | |
| 130 MergedVertexRow(TableTree tree, | |
| 131 TableTreeRow parent, | |
| 132 this.isolate, | |
| 133 this.vertex) | |
| 134 : super(tree, parent) { | |
| 135 } | |
| 136 | |
| 137 bool hasChildren() { | |
| 138 return vertex.outgoingEdges.length > 0 || | |
| 139 vertex.incomingEdges.length > 0; | |
| 140 } | |
| 141 | |
| 142 void onShow() { | |
| 143 super.onShow(); | |
| 144 if (children.length == 0) { | |
| 145 children.add(new MergedEdgesRow(tree, this, isolate, vertex, true)); | |
| 146 children.add(new MergedEdgesRow(tree, this, isolate, vertex, false)); | |
| 147 } | |
| 148 | |
| 149 | |
| 150 var firstColumn = flexColumns[0]; | |
| 151 firstColumn.style.justifyContent = 'flex-start'; | |
| 152 firstColumn.style.position = 'relative'; | |
| 153 firstColumn.style.alignItems = 'center'; | |
| 154 | |
| 155 var percentNode = new SpanElement(); | |
| 156 percentNode.text = "${vertex.instances} instances of"; | |
| 157 percentNode.style.minWidth = '5em'; | |
| 158 percentNode.style.textAlign = 'right'; | |
| 159 firstColumn.children.add(percentNode); | |
| 160 | |
| 161 var gap = new SpanElement(); | |
| 162 gap.style.minWidth = '1em'; | |
| 163 gap.style.display = 'inline-block'; | |
| 164 firstColumn.children.add(gap); | |
| 165 | |
| 166 ClassRefElementWrapper classRef = new Element.tag("class-ref"); | |
| 167 classRef.ref = isolate.getClassByCid(vertex.cid); | |
| 168 classRef.style.alignSelf = 'center'; | |
| 169 firstColumn.children.add(classRef); | |
| 170 | |
| 171 var secondColumn = flexColumns[1]; | |
| 172 secondColumn.style.justifyContent = 'flex-end'; | |
| 173 secondColumn.style.position = 'relative'; | |
| 174 secondColumn.style.alignItems = 'center'; | |
| 175 secondColumn.style.paddingRight = '0.5em'; | |
| 176 secondColumn.text = Utils.formatSize(vertex.shallowSize); | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 class MergedEdgesRow extends TableTreeRow { | |
| 181 final Isolate isolate; | |
| 182 final MergedVertex vertex; | |
| 183 final bool outgoing; | |
| 184 | |
| 185 MergedEdgesRow(TableTree tree, | |
| 186 TableTreeRow parent, | |
| 187 this.isolate, | |
| 188 this.vertex, | |
| 189 this.outgoing) | |
| 190 : super(tree, parent) { | |
| 191 } | |
| 192 | |
| 193 bool hasChildren() { | |
| 194 return outgoing | |
| 195 ? vertex.outgoingEdges.length > 0 | |
| 196 : vertex.incomingEdges.length > 0; | |
| 197 } | |
| 198 | |
| 199 void onShow() { | |
| 200 super.onShow(); | |
| 201 if (children.length == 0) { | |
| 202 if (outgoing) { | |
| 203 var outgoingEdges = vertex.outgoingEdges.values.toList(); | |
| 204 outgoingEdges.sort((a, b) => b.shallowSize - a.shallowSize); | |
| 205 for (var edge in outgoingEdges) { | |
| 206 if (edge.count > 0) { | |
| 207 var row = new MergedEdgeRow(tree, this, isolate, edge, true); | |
| 208 children.add(row); | |
| 209 } | |
| 210 } | |
| 211 } else { | |
| 212 vertex.incomingEdges.sort((a, b) => b.shallowSize - a.shallowSize); | |
| 213 for (var edge in vertex.incomingEdges) { | |
| 214 if (edge.count > 0) { | |
| 215 var row = new MergedEdgeRow(tree, this, isolate, edge, false); | |
| 216 children.add(row); | |
| 217 } | |
| 218 } | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 var count = 0; | |
| 223 var shallowSize = 0; | |
| 224 var edges = outgoing ? vertex.outgoingEdges.values : vertex.incomingEdges; | |
| 225 for (var edge in edges) { | |
| 226 count += edge.count; | |
| 227 shallowSize += edge.shallowSize; | |
| 228 } | |
| 229 | |
| 230 var firstColumn = flexColumns[0]; | |
| 231 firstColumn.style.justifyContent = 'flex-start'; | |
| 232 firstColumn.style.position = 'relative'; | |
| 233 firstColumn.style.alignItems = 'center'; | |
| 234 | |
| 235 var countNode = new SpanElement(); | |
| 236 countNode.text = "$count"; | |
| 237 countNode.style.minWidth = '5em'; | |
| 238 countNode.style.textAlign = 'right'; | |
| 239 firstColumn.children.add(countNode); | |
| 240 | |
| 241 var gap = new SpanElement(); | |
| 242 gap.style.minWidth = '1em'; | |
| 243 gap.style.display = 'inline-block'; | |
| 244 firstColumn.children.add(gap); | |
| 245 | |
| 246 var labelNode = new SpanElement(); | |
| 247 labelNode.text = outgoing ? "Outgoing references" : "Incoming references"; | |
| 248 firstColumn.children.add(labelNode); | |
| 249 | |
| 250 var secondColumn = flexColumns[1]; | |
| 251 secondColumn.style.justifyContent = 'flex-end'; | |
| 252 secondColumn.style.position = 'relative'; | |
| 253 secondColumn.style.alignItems = 'center'; | |
| 254 secondColumn.style.paddingRight = '0.5em'; | |
| 255 secondColumn.text = Utils.formatSize(shallowSize); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 class MergedEdgeRow extends TableTreeRow { | |
| 260 final Isolate isolate; | |
| 261 final MergedEdge edge; | |
| 262 final bool outgoing; | |
| 263 | |
| 264 MergedEdgeRow(TableTree tree, | |
| 265 TableTreeRow parent, | |
| 266 this.isolate, | |
| 267 this.edge, | |
| 268 this.outgoing) | |
| 269 : super(tree, parent) { | |
| 270 } | |
| 271 | |
| 272 bool hasChildren() => false; | |
| 273 | |
| 274 void onShow() { | |
| 275 super.onShow(); | |
| 276 | |
| 277 var firstColumn = flexColumns[0]; | |
| 278 firstColumn.style.justifyContent = 'flex-start'; | |
| 279 firstColumn.style.position = 'relative'; | |
| 280 firstColumn.style.alignItems = 'center'; | |
| 281 | |
| 282 var percentNode = new SpanElement(); | |
| 283 var preposition = outgoing ? "to" : "from"; | |
| 284 percentNode.text = "${edge.count} references $preposition instances of"; | |
| 285 percentNode.style.minWidth = '5em'; | |
| 286 percentNode.style.textAlign = 'right'; | |
| 287 firstColumn.children.add(percentNode); | |
| 288 | |
| 289 var gap = new SpanElement(); | |
| 290 gap.style.minWidth = '1em'; | |
| 291 gap.style.display = 'inline-block'; | |
| 292 firstColumn.children.add(gap); | |
| 293 | |
| 294 MergedVertex v = outgoing ? edge.target : edge.source; | |
| 295 if (v.cid == 0) { | |
| 296 var rootName = new SpanElement(); | |
| 297 rootName.text = '<root>'; | |
| 298 firstColumn.children.add(rootName); | |
| 299 } else { | |
| 300 ClassRefElementWrapper classRef = new Element.tag("class-ref"); | |
| 301 classRef.ref = isolate.getClassByCid(v.cid); | |
| 302 classRef.style.alignSelf = 'center'; | |
| 303 firstColumn.children.add(classRef); | |
| 304 } | |
| 305 | |
| 306 var secondColumn = flexColumns[1]; | |
| 307 secondColumn.style.justifyContent = 'flex-end'; | |
| 308 secondColumn.style.position = 'relative'; | |
| 309 secondColumn.style.alignItems = 'center'; | |
| 310 secondColumn.style.paddingRight = '0.5em'; | |
| 311 secondColumn.text = Utils.formatSize(edge.shallowSize); | |
| 312 } | |
| 313 } | |
| 314 | |
| 315 | |
| 316 class MergedEdge { | |
| 317 final MergedVertex source; | |
| 318 final MergedVertex target; | |
| 319 int count = 0; | |
| 320 int shallowSize = 0; | |
| 321 int retainedSize = 0; | |
| 322 | |
| 323 MergedEdge(this.source, this.target); | |
| 324 } | |
| 325 | |
| 326 class MergedVertex { | |
| 327 final int cid; | |
| 328 int instances = 0; | |
| 329 int shallowSize = 0; | |
| 330 int retainedSize = 0; | |
| 331 | |
| 332 List<MergedEdge> incomingEdges = new List<MergedEdge>(); | |
| 333 Map<int, MergedEdge> outgoingEdges = new Map<int, MergedEdge>(); | |
| 334 | |
| 335 MergedVertex(this.cid); | |
| 336 } | |
| 337 | |
| 338 | |
| 339 Future<List<MergedVertex>> buildMergedVertices(ObjectGraph graph) async { | |
| 340 Logger.root.info("Start merge vertices"); | |
| 341 | |
| 342 var cidToMergedVertex = {}; | |
| 343 | |
| 344 for (var vertex in graph.vertices) { | |
| 345 var cid = vertex.vmCid; | |
| 346 MergedVertex source = cidToMergedVertex[cid]; | |
| 347 if (source == null) { | |
| 348 cidToMergedVertex[cid] = source = new MergedVertex(cid); | |
| 349 } | |
| 350 | |
| 351 source.instances++; | |
| 352 source.shallowSize += (vertex.shallowSize == null ? 0 : vertex.shallowSize); | |
| 353 | |
| 354 for (var vertex2 in vertex.successors) { | |
| 355 var cid2 = vertex2.vmCid; | |
| 356 MergedEdge edge = source.outgoingEdges[cid2]; | |
| 357 if (edge == null) { | |
| 358 MergedVertex target = cidToMergedVertex[cid2]; | |
| 359 if (target == null) { | |
| 360 cidToMergedVertex[cid2] = target = new MergedVertex(cid2); | |
| 361 } | |
| 362 edge = new MergedEdge(source, target); | |
| 363 source.outgoingEdges[cid2] = edge; | |
| 364 target.incomingEdges.add(edge); | |
| 365 } | |
| 366 edge.count++; | |
| 367 // An over-estimate if there are multiple references to the same object. | |
| 368 edge.shallowSize += vertex2.shallowSize == null ? 0 : vertex2.shallowSize; | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 Logger.root.info("End merge vertices"); | |
| 373 | |
| 374 return cidToMergedVertex.values.toList(); | |
| 375 } | |
| 376 | |
| 377 @CustomTag('heap-snapshot') | |
| 378 class HeapSnapshotElement extends ObservatoryElement { | |
| 379 @published Isolate isolate; | |
| 380 @observable HeapSnapshot snapshot; | |
| 381 | |
| 382 @published String state = 'Requested'; | |
| 383 @published String analysisSelector = 'DominatorTree'; | |
| 384 | |
| 385 HeapSnapshotElement.created() : super.created(); | |
| 386 | |
| 387 void analysisSelectorChanged(oldValue) { | |
| 388 _update(); | |
| 389 } | |
| 390 | |
| 391 void isolateChanged(oldValue) { | |
| 392 if (isolate == null) return; | |
| 393 | |
| 394 if (isolate.latestSnapshot == null) { | |
| 395 _getHeapSnapshot(); | |
| 396 } else { | |
| 397 snapshot = isolate.latestSnapshot; | |
| 398 state = 'Loaded'; | |
| 399 _update(); | |
| 400 } | |
| 401 } | |
| 402 | |
| 403 Future refresh() { | |
| 404 return _getHeapSnapshot(); | |
| 405 } | |
| 406 | |
| 407 Future _getHeapSnapshot() { | |
| 408 var completer = new Completer(); | |
| 409 state = "Requesting heap snapshot..."; | |
| 410 isolate.getClassRefs(); | |
| 411 | |
| 412 bool collectGarbage = | |
| 413 app.locationManager.getBoolParameter('collectGarbage', true); | |
| 414 | |
| 415 var stopwatch = new Stopwatch()..start(); | |
| 416 isolate.fetchHeapSnapshot(collectGarbage).listen((event) { | |
| 417 if (event is String) { | |
| 418 print("${stopwatch.elapsedMilliseconds} $event"); | |
| 419 state = event; | |
| 420 } else if (event is HeapSnapshot) { | |
| 421 snapshot = event; | |
| 422 state = 'Loaded'; | |
| 423 completer.complete(snapshot); | |
| 424 _update(); | |
| 425 } else { | |
| 426 throw "Unexpected event $event"; | |
| 427 } | |
| 428 }); | |
| 429 return completer.future; | |
| 430 } | |
| 431 | |
| 432 void _update() { | |
| 433 if (snapshot == null) { | |
| 434 return; | |
| 435 } | |
| 436 | |
| 437 switch(analysisSelector) { | |
| 438 case 'DominatorTree': | |
| 439 _buildDominatorTree(); | |
| 440 break; | |
| 441 case 'MergeByClass': | |
| 442 _buildMergedVertices(); | |
| 443 break; | |
| 444 } | |
| 445 } | |
| 446 | |
| 447 void _buildDominatorTree() { | |
| 448 var tableBody = shadowRoot.querySelector('#treeBody'); | |
| 449 var tree = new TableTree(tableBody, 2); | |
| 450 var rootRow = | |
| 451 new DominatorTreeRow(tree, null, snapshot.graph.root, snapshot); | |
| 452 tree.initialize(rootRow); | |
| 453 return; | |
| 454 } | |
| 455 | |
| 456 void _buildMergedVertices() { | |
| 457 state = 'Grouping...'; | |
| 458 var tableBody = shadowRoot.querySelector('#treeBody'); | |
| 459 var tree = new TableTree(tableBody, 2); | |
| 460 tableBody.children.clear(); | |
| 461 | |
| 462 new Future.delayed(const Duration(milliseconds: 500), () { | |
| 463 buildMergedVertices(snapshot.graph).then((vertices) { | |
| 464 state = 'Loaded'; | |
| 465 var rootRow = new MergedVerticesRow(tree, null, isolate, vertices); | |
| 466 tree.initialize(rootRow); | |
| 467 }); | |
| 468 }); | |
| 469 } | |
| 470 } | |
| OLD | NEW |