OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2017, 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 import 'dart:async'; |
| 6 import 'dart:html'; |
| 7 import 'dart:math' as Math; |
| 8 import 'package:observatory/models.dart' as M; |
| 9 import 'package:observatory/src/elements/class_ref.dart'; |
| 10 import 'package:observatory/src/elements/containers/virtual_tree.dart'; |
| 11 import 'package:observatory/src/elements/helpers/any_ref.dart'; |
| 12 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| 13 import 'package:observatory/src/elements/helpers/tag.dart'; |
| 14 import 'package:observatory/src/elements/helpers/uris.dart'; |
| 15 import 'package:observatory/utils.dart'; |
| 16 |
| 17 class MemorySnapshotElement extends HtmlElement implements Renderable { |
| 18 static const tag = |
| 19 const Tag<MemorySnapshotElement>('memory-snapshot', dependencies: const [ |
| 20 ClassRefElement.tag, |
| 21 VirtualTreeElement.tag, |
| 22 ]); |
| 23 |
| 24 RenderingScheduler<MemorySnapshotElement> _r; |
| 25 |
| 26 Stream<RenderedEvent<MemorySnapshotElement>> get onRendered => _r.onRendered; |
| 27 |
| 28 M.IsolateRef _isolate; |
| 29 M.EditorRepository _editor; |
| 30 M.HeapSnapshotRepository _snapshots; |
| 31 M.ObjectRepository _objects; |
| 32 M.HeapSnapshot _snapshot; |
| 33 Stream<M.HeapSnapshotLoadingProgressEvent> _progressStream; |
| 34 M.HeapSnapshotLoadingProgress _progress; |
| 35 |
| 36 M.IsolateRef get isolate => _isolate; |
| 37 |
| 38 factory MemorySnapshotElement(M.IsolateRef isolate, M.EditorRepository editor, |
| 39 M.HeapSnapshotRepository snapshots, M.ObjectRepository objects, |
| 40 {RenderingQueue queue}) { |
| 41 assert(isolate != null); |
| 42 assert(editor != null); |
| 43 assert(snapshots != null); |
| 44 assert(objects != null); |
| 45 MemorySnapshotElement e = document.createElement(tag.name); |
| 46 e._r = new RenderingScheduler(e, queue: queue); |
| 47 e._isolate = isolate; |
| 48 e._editor = editor; |
| 49 e._snapshots = snapshots; |
| 50 e._objects = objects; |
| 51 return e; |
| 52 } |
| 53 |
| 54 MemorySnapshotElement.created() : super.created(); |
| 55 |
| 56 @override |
| 57 attached() { |
| 58 super.attached(); |
| 59 _r.enable(); |
| 60 _refresh(); |
| 61 } |
| 62 |
| 63 @override |
| 64 detached() { |
| 65 super.detached(); |
| 66 _r.disable(notify: true); |
| 67 children = []; |
| 68 } |
| 69 |
| 70 void render() { |
| 71 if (_progress == null) { |
| 72 children = const []; |
| 73 return; |
| 74 } |
| 75 List<HtmlElement> content; |
| 76 switch (_progress.status) { |
| 77 case M.HeapSnapshotLoadingStatus.fetching: |
| 78 content = _createStatusMessage('Fetching snapshot from VM...', |
| 79 description: _progress.stepDescription, |
| 80 progress: _progress.progress); |
| 81 break; |
| 82 case M.HeapSnapshotLoadingStatus.loading: |
| 83 content = _createStatusMessage('Loading snapshot...', |
| 84 description: _progress.stepDescription, |
| 85 progress: _progress.progress); |
| 86 break; |
| 87 case M.HeapSnapshotLoadingStatus.loaded: |
| 88 content = _createReport(); |
| 89 break; |
| 90 } |
| 91 children = content; |
| 92 } |
| 93 |
| 94 Future reload({bool gc: false}) => _refresh(gc: gc); |
| 95 |
| 96 Future _refresh({bool gc: false}) async { |
| 97 _progress = null; |
| 98 _progressStream = |
| 99 _snapshots.get(isolate, roots: M.HeapSnapshotRoots.user, gc: gc); |
| 100 _r.dirty(); |
| 101 _progressStream.listen((e) { |
| 102 _progress = e.progress; |
| 103 _r.dirty(); |
| 104 }); |
| 105 _progress = (await _progressStream.first).progress; |
| 106 _r.dirty(); |
| 107 if (M.isHeapSnapshotProgressRunning(_progress.status)) { |
| 108 _progress = (await _progressStream.last).progress; |
| 109 _snapshot = _progress.snapshot; |
| 110 _r.dirty(); |
| 111 } |
| 112 } |
| 113 |
| 114 static List<Element> _createStatusMessage(String message, |
| 115 {String description: '', double progress: 0.0}) { |
| 116 return [ |
| 117 new DivElement() |
| 118 ..classes = ['content-centered-big'] |
| 119 ..children = [ |
| 120 new DivElement() |
| 121 ..classes = ['statusBox', 'shadow', 'center'] |
| 122 ..children = [ |
| 123 new DivElement() |
| 124 ..classes = ['statusMessage'] |
| 125 ..text = message, |
| 126 new DivElement() |
| 127 ..classes = ['statusDescription'] |
| 128 ..text = description, |
| 129 new DivElement() |
| 130 ..style.background = '#0489c3' |
| 131 ..style.width = '$progress%' |
| 132 ..style.height = '15px' |
| 133 ..style.borderRadius = '4px' |
| 134 ] |
| 135 ] |
| 136 ]; |
| 137 } |
| 138 |
| 139 VirtualTreeElement _tree; |
| 140 |
| 141 List<Element> _createReport() { |
| 142 final List roots = _getChildrenDominator(_snapshot.dominatorTree); |
| 143 _tree = new VirtualTreeElement( |
| 144 _createDominator, _updateDominator, _getChildrenDominator, |
| 145 items: roots, queue: _r.queue); |
| 146 if (roots.length == 1) { |
| 147 _tree.expand(roots.first, autoExpandSingleChildNodes: true); |
| 148 } |
| 149 final text = 'In a heap dominator tree, an object X is a parent of ' |
| 150 'object Y if every path from the root to Y goes through ' |
| 151 'X. This allows you to find "choke points" that are ' |
| 152 'holding onto a lot of memory. If an object becomes ' |
| 153 'garbage, all its children in the dominator tree become ' |
| 154 'garbage as well. ' |
| 155 'The retained size of an object is the sum of the ' |
| 156 'retained sizes of its children in the dominator tree ' |
| 157 'plus its own shallow size, and is the amount of memory ' |
| 158 'that would be freed if the object became garbage.'; |
| 159 return <HtmlElement>[ |
| 160 new DivElement() |
| 161 ..classes = ['content-centered-big', 'explanation'] |
| 162 ..text = text |
| 163 ..title = text, |
| 164 _tree |
| 165 ]; |
| 166 } |
| 167 |
| 168 static Element _createDominator(toggle) { |
| 169 return new DivElement() |
| 170 ..classes = ['tree-item'] |
| 171 ..children = [ |
| 172 new SpanElement() |
| 173 ..classes = ['size'] |
| 174 ..title = 'retained size', |
| 175 new SpanElement()..classes = ['lines'], |
| 176 new ButtonElement() |
| 177 ..classes = ['expander'] |
| 178 ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), |
| 179 new SpanElement() |
| 180 ..classes = ['percentage'] |
| 181 ..title = 'percentage of heap being retained', |
| 182 new SpanElement()..classes = ['name'] |
| 183 ]; |
| 184 } |
| 185 |
| 186 static const int kMaxChildren = 100; |
| 187 static const int kMinRetainedSize = 4096; |
| 188 |
| 189 static _getChildrenDominator(M.HeapSnapshotDominatorNode node) { |
| 190 final list = node.children.toList(); |
| 191 list.sort((a, b) => b.retainedSize - a.retainedSize); |
| 192 return list |
| 193 .where((child) => child.retainedSize >= kMinRetainedSize) |
| 194 .take(kMaxChildren); |
| 195 } |
| 196 |
| 197 void _updateDominator( |
| 198 HtmlElement element, M.HeapSnapshotDominatorNode node, int depth) { |
| 199 element.children[0].text = Utils.formatSize(node.retainedSize); |
| 200 _updateLines(element.children[1].children, depth); |
| 201 if (_getChildrenDominator(node).isNotEmpty) { |
| 202 element.children[2].text = _tree.isExpanded(node) ? '▼' : '►'; |
| 203 } else { |
| 204 element.children[2].text = ''; |
| 205 } |
| 206 element.children[3].text = |
| 207 Utils.formatPercentNormalized(node.retainedSize * 1.0 / _snapshot.size); |
| 208 final wrapper = new SpanElement() |
| 209 ..classes = ['name'] |
| 210 ..text = 'Loading...'; |
| 211 element.children[4] = wrapper; |
| 212 if (node.isStack) { |
| 213 wrapper |
| 214 ..text = '' |
| 215 ..children = [ |
| 216 new AnchorElement(href: Uris.debugger(isolate))..text = 'stack frames' |
| 217 ]; |
| 218 } else { |
| 219 node.object.then((object) { |
| 220 wrapper |
| 221 ..text = '' |
| 222 ..children = [ |
| 223 anyRef(_isolate, object, _objects, |
| 224 queue: _r.queue, expandable: false) |
| 225 ]; |
| 226 }); |
| 227 } |
| 228 Element.clickEvent |
| 229 .forTarget(element.children[4], useCapture: true) |
| 230 .listen((e) { |
| 231 if (_editor.isAvailable) { |
| 232 e.preventDefault(); |
| 233 _sendNodeToEditor(node); |
| 234 } |
| 235 }); |
| 236 } |
| 237 |
| 238 Future _sendNodeToEditor(M.HeapSnapshotDominatorNode node) async { |
| 239 final object = await node.object; |
| 240 if (node.isStack) { |
| 241 // TODO (https://github.com/flutter/flutter-intellij/issues/1290) |
| 242 // open debugger |
| 243 return new Future.value(); |
| 244 } |
| 245 _editor.openObject(_isolate, object); |
| 246 } |
| 247 |
| 248 static _updateLines(List<Element> lines, int n) { |
| 249 n = Math.max(0, n); |
| 250 while (lines.length > n) { |
| 251 lines.removeLast(); |
| 252 } |
| 253 while (lines.length < n) { |
| 254 lines.add(new SpanElement()); |
| 255 } |
| 256 } |
| 257 |
| 258 static String rootsToString(M.HeapSnapshotRoots roots) { |
| 259 switch (roots) { |
| 260 case M.HeapSnapshotRoots.user: |
| 261 return 'User'; |
| 262 case M.HeapSnapshotRoots.vm: |
| 263 return 'VM'; |
| 264 } |
| 265 throw new Exception('Unknown HeapSnapshotRoots'); |
| 266 } |
| 267 } |
OLD | NEW |