blob: 0913e1ebdb257784f40a1b1f634caa57f11a01fe [file] [log] [blame]
// Copyright (c) 2015, 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.
// Copyright (c) 2013, 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/src/elements/nav/bar.dart';
import 'package:observatory/src/elements/nav/isolate_menu.dart';
import 'package:observatory/src/elements/nav/menu.dart';
import 'package:observatory/src/elements/nav/notify.dart';
import 'package:observatory/src/elements/nav/refresh.dart';
import 'package:observatory/src/elements/nav/top_menu.dart';
import 'package:observatory/src/elements/nav/vm_menu.dart';
import 'package:observatory/utils.dart';
enum HeapSnapshotTreeMode {
dominatorTree,
groupByClass
}
class HeapSnapshotElement extends HtmlElement implements Renderable {
static const tag = const Tag<HeapSnapshotElement>('heap-snapshot',
dependencies: const [
ClassRefElement.tag,
NavBarElement.tag,
NavTopMenuElement.tag,
NavVMMenuElement.tag,
NavIsolateMenuElement.tag,
NavMenuElement.tag,
NavRefreshElement.tag,
NavNotifyElement.tag,
VirtualTreeElement.tag,
]);
RenderingScheduler<HeapSnapshotElement> _r;
Stream<RenderedEvent<HeapSnapshotElement>> get onRendered => _r.onRendered;
M.VM _vm;
M.IsolateRef _isolate;
M.EventRepository _events;
M.NotificationRepository _notifications;
M.HeapSnapshotRepository _snapshots;
M.InstanceRepository _instances;
M.HeapSnapshot _snapshot;
Stream<M.HeapSnapshotLoadingProgressEvent> _progressStream;
M.HeapSnapshotLoadingProgress _progress;
HeapSnapshotTreeMode _mode = HeapSnapshotTreeMode.dominatorTree;
M.IsolateRef get isolate => _isolate;
M.NotificationRepository get notifications => _notifications;
M.HeapSnapshotRepository get profiles => _snapshots;
M.VMRef get vm => _vm;
factory HeapSnapshotElement(M.VM vm, M.IsolateRef isolate,
M.EventRepository events,
M.NotificationRepository notifications,
M.HeapSnapshotRepository snapshots,
M.InstanceRepository instances,
{RenderingQueue queue}) {
assert(vm != null);
assert(isolate != null);
assert(events != null);
assert(notifications != null);
assert(snapshots != null);
assert(instances != null);
HeapSnapshotElement e = document.createElement(tag.name);
e._r = new RenderingScheduler(e, queue: queue);
e._vm = vm;
e._isolate = isolate;
e._events = events;
e._notifications = notifications;
e._snapshots = snapshots;
e._instances = instances;
return e;
}
HeapSnapshotElement.created() : super.created();
@override
attached() {
super.attached();
_r.enable();
_refresh();
}
@override
detached() {
super.detached();
_r.disable(notify: true);
children = [];
}
void render() {
final content = [
new NavBarElement(queue: _r.queue)
..children = [
new NavTopMenuElement(queue: _r.queue),
new NavVMMenuElement(_vm, _events, queue: _r.queue),
new NavIsolateMenuElement(_isolate, _events, queue: _r.queue),
new NavMenuElement('heap snapshot', link: Uris.profiler(_isolate),
last: true, queue: _r.queue),
new NavRefreshElement(queue: _r.queue)
..disabled = M.isHeapSnapshotProgressRunning(_progress?.status)
..onRefresh.listen((e) {
_refresh();
}),
new NavNotifyElement(_notifications, queue: _r.queue)
],
];
if (_progress == null) {
children = content;
return;
}
switch (_progress.status) {
case M.HeapSnapshotLoadingStatus.fetching :
content.addAll(_createStatusMessage('Fetching snapshot from VM...',
description: _progress.stepDescription,
progress: _progress.progress));
break;
case M.HeapSnapshotLoadingStatus.loading :
content.addAll(_createStatusMessage('Loading snapshot...',
description: _progress.stepDescription,
progress: _progress.progress));
break;
case M.HeapSnapshotLoadingStatus.loaded:
content.addAll(_createReport());
break;
}
children = content;
}
Future _refresh() async {
_progress = null;
_progressStream = _snapshots.get(isolate);
_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() {
var report = [
new DivElement()..classes = ['content-centered-big']
..children = [
new DivElement()..classes = ['memberList']
..children = [
new DivElement()..classes = ['memberItem']
..children = [
new DivElement()..classes = ['memberName']
..text = 'Refreshed ',
new DivElement()..classes = ['memberName']
..text = Utils.formatDateTime(_snapshot.timestamp)
],
new DivElement()..classes = ['memberItem']
..children = [
new DivElement()..classes = ['memberName']
..text = 'Objects ',
new DivElement()..classes = ['memberName']
..text = '${_snapshot.objects}'
],
new DivElement()..classes = ['memberItem']
..children = [
new DivElement()..classes = ['memberName']
..text = 'References ',
new DivElement()..classes = ['memberName']
..text = '${_snapshot.references}'
],
new DivElement()..classes = ['memberItem']
..children = [
new DivElement()..classes = ['memberName']
..text = 'Size ',
new DivElement()..classes = ['memberName']
..text = Utils.formatSize(_snapshot.size)
],
new DivElement()..classes = ['memberItem']
..children = [
new DivElement()..classes = ['memberName']
..text = 'Analysis ',
new DivElement()..classes = ['memberName']
..children = _createModeSelect()
]
]
],
];
switch (_mode) {
case HeapSnapshotTreeMode.dominatorTree:
_tree = new VirtualTreeElement(_createDominator, _updateDominator,
_getChildrenDominator,
items: _getChildrenDominator(_snapshot.dominatorTree),
queue: _r.queue);
_tree.expand(_snapshot.dominatorTree);
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.';
report.addAll([
new DivElement()..classes = ['content-centered-big', 'explanation']
..text = text
..title = text,
_tree
]);
break;
case HeapSnapshotTreeMode.groupByClass:
final items = _snapshot.classReferences.toList();
items.sort((a, b) => b.shallowSize - a.shallowSize);
_tree = new VirtualTreeElement(_createGroup, _updateGroup,
_getChildrenGroup, items: items, queue: _r.queue);
_tree.expand(_snapshot.dominatorTree);
report.add(_tree);
break;
default:
break;
}
return report;
}
static Element _createDominator(toggle) {
return new DivElement()
..classes = const ['tree-item']
..children = [
new SpanElement()..classes = const ['size']
..title = 'retained size',
new SpanElement()..classes = const ['lines'],
new ButtonElement()..classes = const ['expander']
..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)),
new SpanElement()..classes = const ['percentage']
..title = 'percentage of heap being retained',
new SpanElement()..classes = const ['name']
];
}
static Element _createGroup(toggle) {
return new DivElement()
..classes = const ['tree-item']
..children = [
new SpanElement()..classes = const ['size']
..title = 'shallow size',
new SpanElement()..classes = const ['lines'],
new ButtonElement()..classes = const ['expander']
..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)),
new SpanElement()..classes = const ['count']
..title = 'shallow size',
new SpanElement()..classes = const ['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);
}
static _getChildrenGroup(item) {
if (item is M.HeapSnapshotClassReferences) {
if (item.inbounds.isNotEmpty || item.outbounds.isNotEmpty) {
return [item.inbounds, item.outbounds];
}
} else if (item is Iterable) {
return item.toList()..sort((a, b) => b.shallowSize - a.shallowSize);
}
return const [];
}
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 = const ['name']
..text = 'Loading...';
element.children[4] = wrapper;
node.object.then((object) {
wrapper..text = ''
..children = [anyRef(_isolate, object, _instances, queue: _r.queue)];
});
}
void _updateGroup(HtmlElement element, item, int depth) {
_updateLines(element.children[1].children, depth);
if (item is M.HeapSnapshotClassReferences) {
element.children[0].text = Utils.formatSize(item.shallowSize);
element.children[2].text = _tree.isExpanded(item) ? '▼' : '►';
element.children[3].text = '${item.instances} instances of ';
element.children[4] = new ClassRefElement(_isolate, item.clazz,
queue: _r.queue)..classes = const ['name'];
} else if (item is Iterable) {
element.children[0].text = '';
if (item.isNotEmpty) {
element.children[2].text = _tree.isExpanded(item) ? '▼' : '►';
} else {
element.children[2].text = '';
}
element.children[3].text = '';
if (item is Iterable<M.HeapSnapshotClassInbound>) {
element.children[4] = new SpanElement()..classes = const ['name']
..text = '${item.length} Incoming references';
} else {
element.children[4] = new SpanElement()..classes = const ['name']
..text = '${item.length} Outgoing references';
}
} else {
element.children[0].text = '';
element.children[2].text = '';
element.children[3].text = '';
element.children[4] = new SpanElement()..classes = const ['name'];
if (item is M.HeapSnapshotClassInbound){
element.children[3].text =
'${item.count} references from instances of ';
element.children[4].children = [
new ClassRefElement(_isolate, item.source,
queue: _r.queue)
];
} else if (item is M.HeapSnapshotClassOutbound){
element.children[3]..text = '${item.count} references to instances of ';
element.children[4].children = [
new ClassRefElement(_isolate, item.target,
queue: _r.queue)
];
}
}
}
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 modeToString(HeapSnapshotTreeMode mode) {
switch (mode) {
case HeapSnapshotTreeMode.dominatorTree: return 'Dominator tree';
case HeapSnapshotTreeMode.groupByClass: return 'Group by class';
}
throw new Exception('Unknown ProfileTreeMode');
}
List<Element> _createModeSelect() {
var s;
return [
s = new SelectElement()..classes = ['analysis-select']
..value = modeToString(_mode)
..children = HeapSnapshotTreeMode.values.map((mode) {
return new OptionElement(value: modeToString(mode),
selected: _mode == mode)
..text = modeToString(mode);
}).toList(growable: false)
..onChange.listen((_) {
_mode = HeapSnapshotTreeMode.values[s.selectedIndex];
_r.dirty();
})
];
}
}