blob: d6adcf3e88176153f445e23e90409eb312a80884 [file] [log] [blame]
// 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 HtmlElement _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 Iterable _getChildrenDominator(nodeDynamic) {
M.HeapSnapshotDominatorNode node = nodeDynamic;
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');
}
}