blob: 1d8c4ba3c904c8b3d36ba0a7c0d7911ea3a9e86b [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.
/// This Element is part of MemoryDashboardElement.
///
/// The Element is stripped down version of AllocationProfileElement where
/// concepts like old and new space has been hidden away.
///
/// For each class in the system it is shown the Total number of instances
/// alive, the Total memory used by these instances, the number of instances
/// created since the last reset, the memory used by these instances.
import 'dart:async';
import 'dart:html';
import 'package:observatory/models.dart' as M;
import 'package:observatory/src/elements/class_ref.dart';
import 'package:observatory/src/elements/containers/virtual_collection.dart';
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/utils.dart';
enum _SortingField {
accumulatedSize,
accumulatedInstances,
currentSize,
currentInstances,
className,
}
enum _SortingDirection { ascending, descending }
class MemoryAllocationsElement extends HtmlElement implements Renderable {
static const tag = const Tag<MemoryAllocationsElement>('memory-allocations',
dependencies: const [ClassRefElement.tag, VirtualCollectionElement.tag]);
RenderingScheduler<MemoryAllocationsElement> _r;
Stream<RenderedEvent<MemoryAllocationsElement>> get onRendered =>
_r.onRendered;
M.IsolateRef _isolate;
M.AllocationProfileRepository _repository;
M.AllocationProfile _profile;
M.EditorRepository _editor;
_SortingField _sortingField = _SortingField.accumulatedInstances;
_SortingDirection _sortingDirection = _SortingDirection.descending;
M.IsolateRef get isolate => _isolate;
factory MemoryAllocationsElement(M.IsolateRef isolate,
M.EditorRepository editor, M.AllocationProfileRepository repository,
{RenderingQueue queue}) {
assert(isolate != null);
assert(editor != null);
assert(repository != null);
MemoryAllocationsElement e = document.createElement(tag.name);
e._r = new RenderingScheduler<MemoryAllocationsElement>(e, queue: queue);
e._isolate = isolate;
e._editor = editor;
e._repository = repository;
return e;
}
MemoryAllocationsElement.created() : super.created();
@override
attached() {
super.attached();
_r.enable();
_refresh();
}
@override
detached() {
super.detached();
_r.disable(notify: true);
children = <Element>[];
}
Future reload({bool gc = false, bool reset = false}) async {
return _refresh(gc: gc, reset: reset);
}
void render() {
if (_profile == null) {
children = <Element>[
new DivElement()
..classes = ['content-centered-big']
..children = <Element>[new HeadingElement.h2()..text = 'Loading...']
];
} else {
children = <Element>[
new VirtualCollectionElement(
_createCollectionLine, _updateCollectionLine,
createHeader: _createCollectionHeader,
search: _search,
items: _profile.members
.where((member) =>
member.newSpace.accumulated.instances != 0 ||
member.newSpace.current.instances != 0 ||
member.oldSpace.accumulated.instances != 0 ||
member.oldSpace.current.instances != 0)
.toList()
..sort(_createSorter()),
queue: _r.queue)
];
}
}
_createSorter() {
var getter;
switch (_sortingField) {
case _SortingField.accumulatedSize:
getter = _getAccumulatedSize;
break;
case _SortingField.accumulatedInstances:
getter = _getAccumulatedInstances;
break;
case _SortingField.currentSize:
getter = _getCurrentSize;
break;
case _SortingField.currentInstances:
getter = _getCurrentInstances;
break;
case _SortingField.className:
getter = (M.ClassHeapStats s) => s.clazz.name;
break;
}
switch (_sortingDirection) {
case _SortingDirection.ascending:
int sort(M.ClassHeapStats a, M.ClassHeapStats b) {
return getter(a).compareTo(getter(b));
}
return sort;
case _SortingDirection.descending:
int sort(M.ClassHeapStats a, M.ClassHeapStats b) {
return getter(b).compareTo(getter(a));
}
return sort;
}
}
static HtmlElement _createCollectionLine() => new DivElement()
..classes = ['collection-item']
..children = <Element>[
new SpanElement()
..classes = ['bytes']
..text = '0B',
new SpanElement()
..classes = ['instances']
..text = '0',
new SpanElement()
..classes = ['bytes']
..text = '0B',
new SpanElement()
..classes = ['instances']
..text = '0',
new SpanElement()..classes = ['name']
];
List<HtmlElement> _createCollectionHeader() {
final resetAccumulators = new ButtonElement();
return [
new DivElement()
..classes = ['collection-item']
..children = <Element>[
new SpanElement()
..classes = ['group']
..nodes = [
new Text('Since Last '),
resetAccumulators
..text = 'Reset'
..title = 'Reset'
..onClick.listen((_) async {
resetAccumulators.disabled = true;
await _refresh(reset: true);
resetAccumulators.disabled = false;
})
],
new SpanElement()
..classes = ['group']
..text = 'Current'
],
new DivElement()
..classes = ['collection-item']
..children = <Element>[
_createHeaderButton(const ['bytes'], 'Size',
_SortingField.accumulatedSize, _SortingDirection.descending),
_createHeaderButton(const ['instances'], 'Instances',
_SortingField.accumulatedInstances, _SortingDirection.descending),
_createHeaderButton(const ['bytes'], 'Size',
_SortingField.currentSize, _SortingDirection.descending),
_createHeaderButton(const ['instances'], 'Instances',
_SortingField.currentInstances, _SortingDirection.descending),
_createHeaderButton(const ['name'], 'Class', _SortingField.className,
_SortingDirection.ascending)
],
];
}
ButtonElement _createHeaderButton(List<String> classes, String text,
_SortingField field, _SortingDirection direction) =>
new ButtonElement()
..classes = classes
..text = _sortingField != field
? text
: _sortingDirection == _SortingDirection.ascending
? '$textâ–¼'
: '$textâ–²'
..onClick.listen((_) => _setSorting(field, direction));
void _setSorting(_SortingField field, _SortingDirection defaultDirection) {
if (_sortingField == field) {
switch (_sortingDirection) {
case _SortingDirection.descending:
_sortingDirection = _SortingDirection.ascending;
break;
case _SortingDirection.ascending:
_sortingDirection = _SortingDirection.descending;
break;
}
} else {
_sortingDirection = defaultDirection;
_sortingField = field;
}
_r.dirty();
}
void _updateCollectionLine(Element e, itemDynamic, index) {
M.ClassHeapStats item = itemDynamic;
e.children[0].text = Utils.formatSize(_getAccumulatedSize(item));
e.children[1].text = '${_getAccumulatedInstances(item)}';
e.children[2].text = Utils.formatSize(_getCurrentSize(item));
e.children[3].text = '${_getCurrentInstances(item)}';
if (item.clazz == null) {
e.children[4] = new SpanElement()
..text = item.displayName
..classes = ['name'];
return;
}
e.children[4] = new ClassRefElement(_isolate, item.clazz, queue: _r.queue)
..classes = ['name'];
Element.clickEvent.forTarget(e.children[4], useCapture: true).listen((e) {
if (_editor.isAvailable) {
e.preventDefault();
_editor.openClass(isolate, item.clazz);
}
});
}
bool _search(Pattern pattern, itemDynamic) {
M.ClassHeapStats item = itemDynamic;
final String value = item.clazz?.name ?? item.displayName;
return value.contains(pattern);
}
Future _refresh({bool gc: false, bool reset: false}) async {
_profile = null;
_r.dirty();
_profile =
await _repository.get(_isolate, gc: gc, reset: reset, combine: true);
_r.dirty();
}
static int _getAccumulatedSize(M.ClassHeapStats s) =>
s.newSpace.accumulated.bytes + s.oldSpace.accumulated.bytes;
static int _getAccumulatedInstances(M.ClassHeapStats s) =>
s.newSpace.accumulated.instances + s.oldSpace.accumulated.instances;
static int _getCurrentSize(M.ClassHeapStats s) =>
s.newSpace.current.bytes + s.oldSpace.current.bytes;
static int _getCurrentInstances(M.ClassHeapStats s) =>
s.newSpace.current.instances + s.oldSpace.current.instances;
}