Chromium Code Reviews| 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..64380ab358ae5b4e765c766e127aa93f5f34f3dc 100644 |
| --- a/runtime/observatory/lib/src/elements/class_view.dart |
| +++ b/runtime/observatory/lib/src/elements/class_view.dart |
| @@ -5,123 +5,470 @@ |
| 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 '; |
| + } |
| + if (_cls.mixin != null) { |
|
rmacnak
2016/08/31 23:51:39
I'd remove this. If class's mixin isn't null, that
cbernaschina
2016/09/01 00:04:07
Done.
|
| + header += 'mixin '; |
| + } |
| + 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( |
|
rmacnak
2016/08/31 23:51:39
This pattern seems to get repeated a lot in many o
cbernaschina
2016/09/01 00:04:07
Acknowledged.
|
| + 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(); |
| } |
| } |