Index: runtime/observatory/lib/src/elements/memory/snapshot.dart |
diff --git a/runtime/observatory/lib/src/elements/memory/snapshot.dart b/runtime/observatory/lib/src/elements/memory/snapshot.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..67be83aa4bca0168c24fe1a8765bceae7ec7e521 |
--- /dev/null |
+++ b/runtime/observatory/lib/src/elements/memory/snapshot.dart |
@@ -0,0 +1,267 @@ |
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+import 'dart:async'; |
+import 'dart:html'; |
+import 'dart:math' as Math; |
+import 'package:observatory/models.dart' as M; |
+import 'package:observatory/src/elements/class_ref.dart'; |
+import 'package:observatory/src/elements/containers/virtual_tree.dart'; |
+import 'package:observatory/src/elements/helpers/any_ref.dart'; |
+import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
+import 'package:observatory/src/elements/helpers/tag.dart'; |
+import 'package:observatory/src/elements/helpers/uris.dart'; |
+import 'package:observatory/utils.dart'; |
+ |
+class MemorySnapshotElement extends HtmlElement implements Renderable { |
+ static const tag = |
+ const Tag<MemorySnapshotElement>('memory-snapshot', dependencies: const [ |
+ ClassRefElement.tag, |
+ VirtualTreeElement.tag, |
+ ]); |
+ |
+ RenderingScheduler<MemorySnapshotElement> _r; |
+ |
+ Stream<RenderedEvent<MemorySnapshotElement>> get onRendered => _r.onRendered; |
+ |
+ M.IsolateRef _isolate; |
+ M.EditorRepository _editor; |
+ M.HeapSnapshotRepository _snapshots; |
+ M.ObjectRepository _objects; |
+ M.HeapSnapshot _snapshot; |
+ Stream<M.HeapSnapshotLoadingProgressEvent> _progressStream; |
+ M.HeapSnapshotLoadingProgress _progress; |
+ |
+ M.IsolateRef get isolate => _isolate; |
+ |
+ factory MemorySnapshotElement(M.IsolateRef isolate, M.EditorRepository editor, |
+ M.HeapSnapshotRepository snapshots, M.ObjectRepository objects, |
+ {RenderingQueue queue}) { |
+ assert(isolate != null); |
+ assert(editor != null); |
+ assert(snapshots != null); |
+ assert(objects != null); |
+ MemorySnapshotElement e = document.createElement(tag.name); |
+ e._r = new RenderingScheduler(e, queue: queue); |
+ e._isolate = isolate; |
+ e._editor = editor; |
+ e._snapshots = snapshots; |
+ e._objects = objects; |
+ return e; |
+ } |
+ |
+ MemorySnapshotElement.created() : super.created(); |
+ |
+ @override |
+ attached() { |
+ super.attached(); |
+ _r.enable(); |
+ _refresh(); |
+ } |
+ |
+ @override |
+ detached() { |
+ super.detached(); |
+ _r.disable(notify: true); |
+ children = []; |
+ } |
+ |
+ void render() { |
+ if (_progress == null) { |
+ children = const []; |
+ return; |
+ } |
+ List<HtmlElement> content; |
+ switch (_progress.status) { |
+ case M.HeapSnapshotLoadingStatus.fetching: |
+ content = _createStatusMessage('Fetching snapshot from VM...', |
+ description: _progress.stepDescription, |
+ progress: _progress.progress); |
+ break; |
+ case M.HeapSnapshotLoadingStatus.loading: |
+ content = _createStatusMessage('Loading snapshot...', |
+ description: _progress.stepDescription, |
+ progress: _progress.progress); |
+ break; |
+ case M.HeapSnapshotLoadingStatus.loaded: |
+ content = _createReport(); |
+ break; |
+ } |
+ children = content; |
+ } |
+ |
+ Future reload({bool gc: false}) => _refresh(gc: gc); |
+ |
+ Future _refresh({bool gc: false}) async { |
+ _progress = null; |
+ _progressStream = |
+ _snapshots.get(isolate, roots: M.HeapSnapshotRoots.user, gc: gc); |
+ _r.dirty(); |
+ _progressStream.listen((e) { |
+ _progress = e.progress; |
+ _r.dirty(); |
+ }); |
+ _progress = (await _progressStream.first).progress; |
+ _r.dirty(); |
+ if (M.isHeapSnapshotProgressRunning(_progress.status)) { |
+ _progress = (await _progressStream.last).progress; |
+ _snapshot = _progress.snapshot; |
+ _r.dirty(); |
+ } |
+ } |
+ |
+ static List<Element> _createStatusMessage(String message, |
+ {String description: '', double progress: 0.0}) { |
+ return [ |
+ new DivElement() |
+ ..classes = ['content-centered-big'] |
+ ..children = [ |
+ new DivElement() |
+ ..classes = ['statusBox', 'shadow', 'center'] |
+ ..children = [ |
+ new DivElement() |
+ ..classes = ['statusMessage'] |
+ ..text = message, |
+ new DivElement() |
+ ..classes = ['statusDescription'] |
+ ..text = description, |
+ new DivElement() |
+ ..style.background = '#0489c3' |
+ ..style.width = '$progress%' |
+ ..style.height = '15px' |
+ ..style.borderRadius = '4px' |
+ ] |
+ ] |
+ ]; |
+ } |
+ |
+ VirtualTreeElement _tree; |
+ |
+ List<Element> _createReport() { |
+ final List roots = _getChildrenDominator(_snapshot.dominatorTree); |
+ _tree = new VirtualTreeElement( |
+ _createDominator, _updateDominator, _getChildrenDominator, |
+ items: roots, queue: _r.queue); |
+ if (roots.length == 1) { |
+ _tree.expand(roots.first, autoExpandSingleChildNodes: true); |
+ } |
+ final text = 'In a heap dominator tree, an object X is a parent of ' |
+ 'object Y if every path from the root to Y goes through ' |
+ 'X. This allows you to find "choke points" that are ' |
+ 'holding onto a lot of memory. If an object becomes ' |
+ 'garbage, all its children in the dominator tree become ' |
+ 'garbage as well. ' |
+ 'The retained size of an object is the sum of the ' |
+ 'retained sizes of its children in the dominator tree ' |
+ 'plus its own shallow size, and is the amount of memory ' |
+ 'that would be freed if the object became garbage.'; |
+ return <HtmlElement>[ |
+ new DivElement() |
+ ..classes = ['content-centered-big', 'explanation'] |
+ ..text = text |
+ ..title = text, |
+ _tree |
+ ]; |
+ } |
+ |
+ static Element _createDominator(toggle) { |
+ return new DivElement() |
+ ..classes = ['tree-item'] |
+ ..children = [ |
+ new SpanElement() |
+ ..classes = ['size'] |
+ ..title = 'retained size', |
+ new SpanElement()..classes = ['lines'], |
+ new ButtonElement() |
+ ..classes = ['expander'] |
+ ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), |
+ new SpanElement() |
+ ..classes = ['percentage'] |
+ ..title = 'percentage of heap being retained', |
+ new SpanElement()..classes = ['name'] |
+ ]; |
+ } |
+ |
+ static const int kMaxChildren = 100; |
+ static const int kMinRetainedSize = 4096; |
+ |
+ static _getChildrenDominator(M.HeapSnapshotDominatorNode node) { |
+ final list = node.children.toList(); |
+ list.sort((a, b) => b.retainedSize - a.retainedSize); |
+ return list |
+ .where((child) => child.retainedSize >= kMinRetainedSize) |
+ .take(kMaxChildren); |
+ } |
+ |
+ void _updateDominator( |
+ HtmlElement element, M.HeapSnapshotDominatorNode node, int depth) { |
+ element.children[0].text = Utils.formatSize(node.retainedSize); |
+ _updateLines(element.children[1].children, depth); |
+ if (_getChildrenDominator(node).isNotEmpty) { |
+ element.children[2].text = _tree.isExpanded(node) ? '▼' : '►'; |
+ } else { |
+ element.children[2].text = ''; |
+ } |
+ element.children[3].text = |
+ Utils.formatPercentNormalized(node.retainedSize * 1.0 / _snapshot.size); |
+ final wrapper = new SpanElement() |
+ ..classes = ['name'] |
+ ..text = 'Loading...'; |
+ element.children[4] = wrapper; |
+ if (node.isStack) { |
+ wrapper |
+ ..text = '' |
+ ..children = [ |
+ new AnchorElement(href: Uris.debugger(isolate))..text = 'stack frames' |
+ ]; |
+ } else { |
+ node.object.then((object) { |
+ wrapper |
+ ..text = '' |
+ ..children = [ |
+ anyRef(_isolate, object, _objects, |
+ queue: _r.queue, expandable: false) |
+ ]; |
+ }); |
+ } |
+ Element.clickEvent |
+ .forTarget(element.children[4], useCapture: true) |
+ .listen((e) { |
+ if (_editor.isAvailable) { |
+ e.preventDefault(); |
+ _sendNodeToEditor(node); |
+ } |
+ }); |
+ } |
+ |
+ Future _sendNodeToEditor(M.HeapSnapshotDominatorNode node) async { |
+ final object = await node.object; |
+ if (node.isStack) { |
+ // TODO (https://github.com/flutter/flutter-intellij/issues/1290) |
+ // open debugger |
+ return new Future.value(); |
+ } |
+ _editor.openObject(_isolate, object); |
+ } |
+ |
+ static _updateLines(List<Element> lines, int n) { |
+ n = Math.max(0, n); |
+ while (lines.length > n) { |
+ lines.removeLast(); |
+ } |
+ while (lines.length < n) { |
+ lines.add(new SpanElement()); |
+ } |
+ } |
+ |
+ static String rootsToString(M.HeapSnapshotRoots roots) { |
+ switch (roots) { |
+ case M.HeapSnapshotRoots.user: |
+ return 'User'; |
+ case M.HeapSnapshotRoots.vm: |
+ return 'VM'; |
+ } |
+ throw new Exception('Unknown HeapSnapshotRoots'); |
+ } |
+} |