| 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');
|
| + }
|
| +}
|
|
|