Chromium Code Reviews| 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..45c6651cbd693bcf9ebcc6d1ef04dc61c5187ef2 |
| --- /dev/null |
| +++ b/runtime/observatory/lib/src/elements/memory/snapshot.dart |
| @@ -0,0 +1,270 @@ |
| +// 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. |
| + |
| +// 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/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(cbernaschina) open debugger? |
|
siva
2017/08/19 00:35:46
Maybe open an issue and refer to this using the is
|
| + 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'); |
| + } |
| +} |