| Index: runtime/observatory/lib/src/elements/class_view.dart
|
| diff --git a/runtime/observatory/lib/src/elements/class_view.dart b/runtime/observatory/lib/src/elements/class_view.dart
|
| index d883fb27c86306e8a2698d93479196a4410618cf..b16d68216562d47da384ca94441ff4485d6e0f04 100644
|
| --- a/runtime/observatory/lib/src/elements/class_view.dart
|
| +++ b/runtime/observatory/lib/src/elements/class_view.dart
|
| @@ -5,123 +5,467 @@
|
| library class_view_element;
|
|
|
| import 'dart:async';
|
| -import 'observatory_element.dart';
|
| -import 'sample_buffer_control.dart';
|
| -import 'stack_trace_tree_config.dart';
|
| -import 'cpu_profile/virtual_tree.dart';
|
| -import 'package:observatory/heap_snapshot.dart';
|
| -import 'package:observatory/elements.dart';
|
| +import 'dart:html';
|
| import 'package:observatory/models.dart' as M;
|
| -import 'package:observatory/service.dart';
|
| -import 'package:observatory/repositories.dart';
|
| -import 'package:polymer/polymer.dart';
|
| -
|
| -@CustomTag('class-view')
|
| -class ClassViewElement extends ObservatoryElement {
|
| - @published Class cls;
|
| - @observable ServiceMap instances;
|
| - @observable int reachableBytes;
|
| - @observable int retainedBytes;
|
| - @observable ObservableList mostRetained;
|
| - SampleBufferControlElement sampleBufferControlElement;
|
| - StackTraceTreeConfigElement stackTraceTreeConfigElement;
|
| - CpuProfileVirtualTreeElement cpuProfileTreeElement;
|
| - ClassSampleProfileRepository repository = new ClassSampleProfileRepository();
|
| +import 'package:observatory/src/elements/class_allocation_profile.dart';
|
| +import 'package:observatory/src/elements/class_instances.dart';
|
| +import 'package:observatory/src/elements/class_ref.dart';
|
| +import 'package:observatory/src/elements/curly_block.dart';
|
| +import 'package:observatory/src/elements/error_ref.dart';
|
| +import 'package:observatory/src/elements/eval_box.dart';
|
| +import 'package:observatory/src/elements/field_ref.dart';
|
| +import 'package:observatory/src/elements/function_ref.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/instance_ref.dart';
|
| +import 'package:observatory/src/elements/library_ref.dart';
|
| +import 'package:observatory/src/elements/nav/bar.dart';
|
| +import 'package:observatory/src/elements/nav/class_menu.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/src/elements/object_common.dart';
|
| +import 'package:observatory/src/elements/source_inset.dart';
|
| +import 'package:observatory/src/elements/source_link.dart';
|
| +import 'package:observatory/src/elements/view_footer.dart';
|
|
|
| +class ClassViewElement extends HtmlElement implements Renderable {
|
| + static const tag = const Tag<ClassViewElement>('class-view',
|
| + dependencies: const [
|
| + ClassInstancesElement.tag,
|
| + ClassRefElement.tag,
|
| + CurlyBlockElement.tag,
|
| + ErrorRefElement.tag,
|
| + EvalBoxElement.tag,
|
| + FieldRefElement.tag,
|
| + FunctionRefElement.tag,
|
| + InstanceRefElement.tag,
|
| + LibraryRefElement.tag,
|
| + NavBarElement.tag,
|
| + NavClassMenuElement.tag,
|
| + NavTopMenuElement.tag,
|
| + NavVMMenuElement.tag,
|
| + NavIsolateMenuElement.tag,
|
| + NavMenuElement.tag,
|
| + NavRefreshElement.tag,
|
| + NavNotifyElement.tag,
|
| + ObjectCommonElement.tag,
|
| + SourceInsetElement.tag,
|
| + SourceLinkElement.tag,
|
| + ViewFooterElement.tag
|
| + ]);
|
|
|
| - ClassViewElement.created() : super.created();
|
| + RenderingScheduler<ClassViewElement> _r;
|
|
|
| - Future<ServiceObject> evaluate(String expression) {
|
| - return cls.evaluate(expression);
|
| - }
|
| + Stream<RenderedEvent<ClassViewElement>> get onRendered => _r.onRendered;
|
|
|
| - Future<ServiceObject> reachable(var limit) {
|
| - return cls.isolate.getInstances(cls, limit).then((ServiceMap obj) {
|
| - instances = obj;
|
| - });
|
| - }
|
| + M.VM _vm;
|
| + M.IsolateRef _isolate;
|
| + M.EventRepository _events;
|
| + M.NotificationRepository _notifications;
|
| + M.Class _cls;
|
| + M.ClassRepository _classes;
|
| + M.RetainedSizeRepository _retainedSizes;
|
| + M.ReachableSizeRepository _reachableSizes;
|
| + M.InboundReferencesRepository _references;
|
| + M.RetainingPathRepository _retainingPaths;
|
| + M.StronglyReachableInstancesRepository _stronglyReachableInstances;
|
| + M.TopRetainingInstancesRepository _topRetainedInstances;
|
| + M.FieldRepository _fields;
|
| + M.ScriptRepository _scripts;
|
| + M.InstanceRepository _instances;
|
| + M.EvalRepository _eval;
|
| + M.ClassSampleProfileRepository _profiles;
|
| + Iterable<M.Field> _classFields;
|
|
|
| - Future retainedToplist(var limit) async {
|
| - final raw = await cls.isolate.fetchHeapSnapshot(true).last;
|
| - final snapshot = new HeapSnapshot();
|
| - await snapshot.loadProgress(cls.isolate, raw).last;
|
| - final most = await Future.wait(snapshot.getMostRetained(cls.isolate,
|
| - classId: cls.vmCid,
|
| - limit: 10));
|
| - mostRetained = new ObservableList.from(most);
|
| - }
|
|
|
| - // TODO(koda): Add no-arg "calculate-link" instead of reusing "eval-link".
|
| - Future<ServiceObject> reachableSize(var dummy) {
|
| - return cls.isolate.getReachableSize(cls).then((Instance obj) {
|
| - reachableBytes = int.parse(obj.valueAsString);
|
| - });
|
| - }
|
| + M.VMRef get vm => _vm;
|
| + M.IsolateRef get isolate => _isolate;
|
| + M.NotificationRepository get notifications => _notifications;
|
| + M.Class get cls => _cls;
|
|
|
| - Future<ServiceObject> retainedSize(var dummy) {
|
| - return cls.isolate.getRetainedSize(cls).then((Instance obj) {
|
| - retainedBytes = int.parse(obj.valueAsString);
|
| - });
|
| + factory ClassViewElement(M.VM vm, M.IsolateRef isolate, M.Class cls,
|
| + M.EventRepository events,
|
| + M.NotificationRepository notifications,
|
| + M.ClassRepository classes,
|
| + M.RetainedSizeRepository retainedSizes,
|
| + M.ReachableSizeRepository reachableSizes,
|
| + M.InboundReferencesRepository references,
|
| + M.RetainingPathRepository retainingPaths,
|
| + M.FieldRepository fields,
|
| + M.ScriptRepository scripts,
|
| + M.InstanceRepository instances,
|
| + M.EvalRepository eval,
|
| + M.StronglyReachableInstancesRepository stronglyReachable,
|
| + M.TopRetainingInstancesRepository topRetained,
|
| + M.ClassSampleProfileRepository profiles,
|
| + {RenderingQueue queue}) {
|
| + assert(vm != null);
|
| + assert(isolate != null);
|
| + assert(events != null);
|
| + assert(notifications != null);
|
| + assert(cls != null);
|
| + assert(classes != null);
|
| + assert(retainedSizes != null);
|
| + assert(reachableSizes != null);
|
| + assert(references != null);
|
| + assert(retainingPaths != null);
|
| + assert(fields != null);
|
| + assert(scripts != null);
|
| + assert(instances != null);
|
| + assert(eval != null);
|
| + assert(stronglyReachable != null);
|
| + assert(topRetained != null);
|
| + assert(profiles != null);
|
| + ClassViewElement 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._cls = cls;
|
| + e._classes = classes;
|
| + e._retainedSizes = retainedSizes;
|
| + e._reachableSizes = reachableSizes;
|
| + e._references = references;
|
| + e._retainingPaths = retainingPaths;
|
| + e._fields = fields;
|
| + e._scripts = scripts;
|
| + e._instances = instances;
|
| + e._eval = eval;
|
| + e._stronglyReachableInstances = stronglyReachable;
|
| + e._topRetainedInstances = topRetained;
|
| + e._profiles = profiles;
|
| + return e;
|
| }
|
|
|
| - void attached() {
|
| + ClassViewElement.created() : super.created();
|
| +
|
| + @override
|
| + attached() {
|
| super.attached();
|
| - cls.fields.forEach((field) => field.reload());
|
| + _r.enable();
|
| + _loadAdditionalData();
|
| }
|
|
|
| - Future refresh() async {
|
| - instances = null;
|
| - retainedBytes = null;
|
| - mostRetained = null;
|
| - await cls.reload();
|
| - await Future.wait(cls.fields.map((field) => field.reload()));
|
| + @override
|
| + detached() {
|
| + super.detached();
|
| + _r.disable(notify: true);
|
| + children = [];
|
| }
|
|
|
| - M.SampleProfileTag _tag = M.SampleProfileTag.none;
|
| -
|
| - Future refreshAllocationProfile() async {
|
| - shadowRoot.querySelector('#sampleBufferControl').children = const [];
|
| - shadowRoot.querySelector('#stackTraceTreeConfig').children = const [];
|
| - shadowRoot.querySelector('#cpuProfileTree').children = const [];
|
| - final stream = repository.get(cls, _tag);
|
| - var progress = (await stream.first).progress;
|
| - shadowRoot.querySelector('#sampleBufferControl')..children = [
|
| - new SampleBufferControlElement(progress, stream, queue: app.queue,
|
| - selectedTag: _tag)
|
| - ..onTagChange.listen((e) {
|
| - _tag = e.element.selectedTag;
|
| - refreshAllocationProfile();
|
| - })
|
| + ObjectCommonElement _common;
|
| + ClassInstancesElement _classInstances;
|
| + bool _loadProfile = false;
|
| +
|
| + void render() {
|
| + _common = _common ?? new ObjectCommonElement(_isolate, _cls, _retainedSizes,
|
| + _reachableSizes, _references, _retainingPaths, _instances,
|
| + queue: _r.queue);
|
| + _classInstances = _classInstances ?? new ClassInstancesElement(_isolate,
|
| + _cls, _retainedSizes, _reachableSizes, _stronglyReachableInstances,
|
| + _topRetainedInstances, _instances, queue: _r.queue);
|
| + var header = '';
|
| + if (_cls.isAbstract) {
|
| + header += 'abstract ';
|
| + }
|
| + if (_cls.isPatch) {
|
| + header += 'patch ';
|
| + }
|
| + children = [
|
| + 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 NavClassMenuElement(_isolate, _cls, queue: _r.queue),
|
| + new NavRefreshElement(label: 'Refresh Allocation Profile',
|
| + queue: _r.queue)
|
| + ..onRefresh.listen((e) {
|
| + e.element.disabled = true;
|
| + _loadProfile = true;
|
| + _r.dirty();
|
| + }),
|
| + new NavRefreshElement(queue: _r.queue)
|
| + ..onRefresh.listen((e) {
|
| + e.element.disabled = true;
|
| + _common = null;
|
| + _classInstances = null;
|
| + _fieldsExpanded = null;
|
| + _functionsExpanded = null;
|
| + _refresh();
|
| + }),
|
| + new NavNotifyElement(_notifications, queue: _r.queue)
|
| + ],
|
| + new DivElement()..classes = ['content-centered-big']
|
| + ..children = [
|
| + new HeadingElement.h2()..text = '$header class ${_cls.name}',
|
| + new HRElement(),
|
| + _common,
|
| + new BRElement(),
|
| + new DivElement()..classes = ['memberList']
|
| + ..children = _createMembers(),
|
| + new DivElement()
|
| + ..children = _cls.error == null
|
| + ? const []
|
| + : [
|
| + new HRElement(),
|
| + new ErrorRefElement(_cls.error, queue: _r.queue)
|
| + ],
|
| + new HRElement(),
|
| + new EvalBoxElement(_isolate, _cls, _instances, _eval,
|
| + queue: _r.queue),
|
| + new HRElement(),
|
| + new HeadingElement.h2()..text = 'Fields & Functions',
|
| + new DivElement()..classes = ['memberList']
|
| + ..children = _createElements(),
|
| + new HRElement(),
|
| + new HeadingElement.h2()..text = 'Instances',
|
| + new DivElement()
|
| + ..children = _cls.hasAllocations
|
| + ? [_classInstances]
|
| + : const [],
|
| + new HRElement(),
|
| + new HeadingElement.h2()..text = 'Allocations',
|
| + new DivElement()..classes = ['memberList']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'Tracing allocations? ',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = _cls.traceAllocations
|
| + ? [
|
| + new SpanElement()..text = 'Yes ',
|
| + new ButtonElement()..text = 'disable'
|
| + ..onClick.listen((e) async {
|
| + e.target.disabled = true;
|
| + await _profiles.disable(_isolate, _cls);
|
| + _loadProfile = true;
|
| + _refresh();
|
| + })
|
| + ]
|
| + : [
|
| + new SpanElement()..text = 'No ',
|
| + new ButtonElement()..text = 'enable'
|
| + ..onClick.listen((e) async {
|
| + e.target.disabled = true;
|
| + await _profiles.enable(_isolate, _cls);
|
| + _refresh();
|
| + })
|
| + ]
|
| + ],
|
| + new DivElement()
|
| + ..children = _loadProfile
|
| + ? [new ClassAllocationProfileElement(_isolate, _cls, _profiles,
|
| + queue: _r.queue)]
|
| + : const [],
|
| + new DivElement()
|
| + ..children = _cls.location != null
|
| + ? [new HRElement(),
|
| + new SourceInsetElement(_isolate, _cls.location, _scripts,
|
| + _instances, _events, queue: _r.queue)]
|
| + : const [],
|
| + new HRElement(),
|
| + new ViewFooterElement(queue: _r.queue)
|
| + ]
|
| ];
|
| - if (M.isSampleProcessRunning(progress.status)) {
|
| - progress = (await stream.last).progress;
|
| + }
|
| +
|
| + bool _fieldsExpanded;
|
| + bool _functionsExpanded;
|
| +
|
| + List<Element> _createMembers() {
|
| + final members = <Element>[];
|
| + if (_cls.library != null) {
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'library',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = [
|
| + new LibraryRefElement(_isolate, _cls.library, queue: _r.queue)
|
| + ]
|
| + ]
|
| + );
|
| }
|
| - if (progress.status == M.SampleProfileLoadingStatus.loaded) {
|
| - shadowRoot.querySelector('#stackTraceTreeConfig')..children = [
|
| - new StackTraceTreeConfigElement(
|
| - queue: app.queue)
|
| - ..showFilter = false
|
| - ..onModeChange.listen((e) {
|
| - cpuProfileTreeElement.mode = e.element.mode;
|
| - })
|
| - ..onDirectionChange.listen((e) {
|
| - cpuProfileTreeElement.direction = e.element.direction;
|
| - })
|
| - ];
|
| - shadowRoot.querySelector('#cpuProfileTree')..children = [
|
| - cpuProfileTreeElement = new CpuProfileVirtualTreeElement(cls.isolate,
|
| - progress.profile, queue: app.queue)
|
| - ];
|
| + if (_cls.location != null) {
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'script',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = [
|
| + new SourceLinkElement(_isolate, _cls.location, _scripts,
|
| + queue: _r.queue)
|
| + ]
|
| + ]
|
| + );
|
| }
|
| + if (_cls.superclass != null) {
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'superclass',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = [
|
| + new ClassRefElement(_isolate, _cls.superclass, queue: _r.queue)
|
| + ]
|
| + ]
|
| + );
|
| + }
|
| + if (_cls.superType != null) {
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'supertype',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = [
|
| + new InstanceRefElement(_isolate, _cls.superType, _instances,
|
| + queue: _r.queue)
|
| + ]
|
| + ]
|
| + );
|
| + }
|
| + if (cls.mixin != null) {
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'mixin',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = [
|
| + new InstanceRefElement(_isolate, _cls.mixin, _instances,
|
| + queue: _r.queue)
|
| + ]
|
| + ]
|
| + );
|
| + }
|
| + if (_cls.subclasses.length > 0) {
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'extended by',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = (_cls.subclasses.expand((subcls) => [
|
| + new ClassRefElement(_isolate, subcls, queue: _r.queue),
|
| + new SpanElement()..text = ', '
|
| + ]).toList()..removeLast())
|
| + ]
|
| + );
|
| + }
|
| +
|
| + members.add(new BRElement());
|
| +
|
| + if (_cls.interfaces.length > 0) {
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'implements',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = (_cls.interfaces.expand((interf) => [
|
| + new InstanceRefElement(_isolate, interf, _instances,
|
| + queue: _r.queue),
|
| + new SpanElement()..text = ', '
|
| + ]).toList()..removeLast())
|
| + ]
|
| + );
|
| + }
|
| + if (_cls.name != _cls.vmName) {
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'vm name',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..text = '${_cls.vmName}'
|
| + ]
|
| + );
|
| + }
|
| + return members;
|
| }
|
|
|
| - Future toggleAllocationTrace() {
|
| - if (cls == null) {
|
| - return new Future(refresh);
|
| + List<Element> _createElements() {
|
| + final members = <Element>[];
|
| + if (_classFields != null && _classFields.isNotEmpty) {
|
| + final fields = _classFields.toList();
|
| + _fieldsExpanded = _fieldsExpanded ?? (fields.length <= 8);
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'fields ${fields.length}',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children =[
|
| + new CurlyBlockElement(expanded: _fieldsExpanded)
|
| + ..onToggle.listen((e) => _fieldsExpanded = e.control.expanded)
|
| + ..children = [
|
| + new DivElement()..classes = ['memberList']
|
| + ..children = (fields.map((f) =>
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..children =[
|
| + new FieldRefElement(_isolate, f, _instances,
|
| + queue: _r.queue)
|
| + ],
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children = [
|
| + anyRef(_isolate, f.staticValue, _instances,
|
| + queue: _r.queue)
|
| + ]
|
| + ]
|
| + ).toList())
|
| + ]
|
| + ]
|
| + ]
|
| + );
|
| }
|
| - if (cls.traceAllocations) {
|
| - refreshAllocationProfile();
|
| +
|
| + if (_cls.functions.isNotEmpty) {
|
| + final functions = _cls.functions.toList();
|
| + _functionsExpanded = _functionsExpanded ?? (functions.length <= 8);
|
| + members.add(
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'functions (${functions.length})',
|
| + new DivElement()..classes = ['memberValue']
|
| + ..children =[
|
| + new CurlyBlockElement(expanded: _functionsExpanded)
|
| + ..onToggle.listen((e) =>
|
| + _functionsExpanded = e.control.expanded)
|
| + ..children = (functions.map((f) =>
|
| + new DivElement()..classes = ['indent']
|
| + ..children = [
|
| + new FunctionRefElement(_isolate, f, queue: _r.queue)
|
| + ]
|
| + ).toList())
|
| + ]
|
| + ]
|
| + );
|
| }
|
| - return cls.setTraceAllocations(!cls.traceAllocations).whenComplete(refresh);
|
| + return members;
|
| + }
|
| +
|
| + Future _refresh() async {
|
| + _cls = await _classes.get(_isolate, _cls.id);
|
| + await _loadAdditionalData();
|
| + _r.dirty();
|
| + }
|
| +
|
| + Future _loadAdditionalData() async {
|
| + _classFields = await Future.wait(_cls.fields.map((f)
|
| + => _fields.get(_isolate, f.id)));
|
| + _r.dirty();
|
| }
|
| }
|
|
|