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(); |
} |
} |