| // 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 'package:observatory/models.dart' as M; |
| import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| import 'package:observatory/src/elements/helpers/custom_element.dart'; |
| import 'package:observatory/utils.dart'; |
| |
| class SampleBufferControlChangedElement { |
| final SampleBufferControlElement element; |
| SampleBufferControlChangedElement(this.element); |
| } |
| |
| class SampleBufferControlElement extends CustomElement implements Renderable { |
| late RenderingScheduler<SampleBufferControlElement> _r; |
| |
| Stream<RenderedEvent<SampleBufferControlElement>> get onRendered => |
| _r.onRendered; |
| |
| StreamController<SampleBufferControlChangedElement> _onTagChange = |
| new StreamController<SampleBufferControlChangedElement>.broadcast(); |
| Stream<SampleBufferControlChangedElement> get onTagChange => |
| _onTagChange.stream; |
| |
| late M.VM _vm; |
| late Stream<M.SampleProfileLoadingProgressEvent> _progressStream; |
| late M.SampleProfileLoadingProgress _progress; |
| late M.SampleProfileTag _tag; |
| bool _showTag = false; |
| bool _profileVM = false; |
| late StreamSubscription _subscription; |
| |
| M.SampleProfileLoadingProgress get progress => _progress; |
| M.SampleProfileTag get selectedTag => _tag; |
| bool get showTag => _showTag; |
| bool get profileVM => _profileVM; |
| |
| set selectedTag(M.SampleProfileTag value) => |
| _tag = _r.checkAndReact(_tag, value); |
| set showTag(bool value) => _showTag = _r.checkAndReact(_showTag, value); |
| set profileVM(bool value) => _profileVM = _r.checkAndReact(_profileVM, value); |
| |
| factory SampleBufferControlElement( |
| M.VM vm, |
| M.SampleProfileLoadingProgress progress, |
| Stream<M.SampleProfileLoadingProgressEvent> progressStream, |
| {M.SampleProfileTag selectedTag: M.SampleProfileTag.none, |
| bool showTag: true, |
| RenderingQueue? queue}) { |
| assert(progress != null); |
| assert(progressStream != null); |
| assert(selectedTag != null); |
| assert(showTag != null); |
| SampleBufferControlElement e = new SampleBufferControlElement.created(); |
| e._r = new RenderingScheduler<SampleBufferControlElement>(e, queue: queue); |
| e._vm = vm; |
| e._progress = progress; |
| e._progressStream = progressStream; |
| e._tag = selectedTag; |
| e._showTag = showTag; |
| return e; |
| } |
| |
| SampleBufferControlElement.created() : super.created('sample-buffer-control'); |
| |
| @override |
| void attached() { |
| super.attached(); |
| _r.enable(); |
| _subscription = _progressStream.listen((e) { |
| _progress = e.progress; |
| _r.dirty(); |
| }); |
| } |
| |
| @override |
| void detached() { |
| super.detached(); |
| _r.disable(notify: true); |
| children = const []; |
| _subscription.cancel(); |
| } |
| |
| void render() { |
| var content = <Element>[ |
| new HeadingElement.h2()..text = 'Sample buffer', |
| new HRElement() |
| ]; |
| switch (_progress.status) { |
| case M.SampleProfileLoadingStatus.fetching: |
| content.addAll(_createStatusMessage('Fetching profile from VM...')); |
| break; |
| case M.SampleProfileLoadingStatus.loading: |
| content.addAll(_createStatusMessage('Loading profile...', |
| progress: _progress.progress)); |
| break; |
| case M.SampleProfileLoadingStatus.disabled: |
| content.addAll(_createDisabledMessage()); |
| break; |
| case M.SampleProfileLoadingStatus.loaded: |
| content.addAll(_createStatusReport()); |
| break; |
| } |
| children = <Element>[ |
| new DivElement() |
| ..classes = ['content-centered-big'] |
| ..children = content |
| ]; |
| } |
| |
| static List<Element> _createStatusMessage(String message, |
| {double progress: 0.0}) { |
| return [ |
| new DivElement() |
| ..classes = ['statusBox', 'shadow', 'center'] |
| ..children = <Element>[ |
| new DivElement() |
| ..classes = ['statusMessage'] |
| ..text = message, |
| new DivElement() |
| ..style.background = '#0489c3' |
| ..style.width = '$progress%' |
| ..style.height = '15px' |
| ..style.borderRadius = '4px' |
| ] |
| ]; |
| } |
| |
| List<Element> _createDisabledMessage() { |
| return [ |
| new DivElement() |
| ..classes = ['statusBox' 'shadow' 'center'] |
| ..children = <Element>[ |
| new DivElement() |
| ..children = <Element>[ |
| new HeadingElement.h1()..text = 'Profiling is disabled', |
| new BRElement(), |
| new DivElement() |
| ..innerHtml = 'Perhaps the <b>profile</b> ' |
| 'flag has been disabled for this VM.', |
| new BRElement(), |
| new ButtonElement() |
| ..text = 'Enable profiler' |
| ..onClick.listen((_) { |
| _enableProfiler(); |
| }) |
| ] |
| ] |
| ]; |
| } |
| |
| List<Element> _createStatusReport() { |
| final fetchT = Utils.formatDurationInSeconds(_progress.fetchingTime); |
| final loadT = Utils.formatDurationInSeconds(_progress.loadingTime); |
| final sampleCount = _progress.profile.sampleCount; |
| final refreshT = new DateTime.now(); |
| final maxStackDepth = _progress.profile.maxStackDepth; |
| final sampleRate = _progress.profile.sampleRate.toStringAsFixed(0); |
| final timeSpan = _progress.profile.sampleCount == 0 |
| ? '0s' |
| : Utils.formatTimePrecise(_progress.profile.timeSpan); |
| |
| var content = <Element>[ |
| new DivElement() |
| ..classes = ['memberItem'] |
| ..children = <Element>[ |
| new DivElement() |
| ..classes = ['memberName'] |
| ..text = 'Refreshed at', |
| new DivElement() |
| ..classes = ['memberValue'] |
| ..text = '$refreshT (fetched in ${fetchT}s) (loaded in ${loadT}s)' |
| ], |
| new DivElement() |
| ..classes = ['memberItem'] |
| ..children = <Element>[ |
| new DivElement() |
| ..classes = ['memberName'] |
| ..text = 'Profile contains ', |
| new DivElement() |
| ..classes = ['memberValue'] |
| ..text = '$sampleCount samples (spanning $timeSpan)' |
| ], |
| new DivElement() |
| ..classes = ['memberItem'] |
| ..children = <Element>[ |
| new DivElement() |
| ..classes = ['memberName'] |
| ..text = 'Sampling', |
| new DivElement() |
| ..classes = ['memberValue'] |
| ..text = '$maxStackDepth stack frames @ ${sampleRate}Hz' |
| ], |
| ]; |
| if (_showTag) { |
| content.add(new DivElement() |
| ..classes = ['memberItem'] |
| ..children = <Element>[ |
| new DivElement() |
| ..classes = ['memberName'] |
| ..text = 'Tag Order', |
| new DivElement() |
| ..classes = ['memberValue'] |
| ..children = _createTagSelect() |
| ]); |
| } |
| return [ |
| new DivElement() |
| ..classes = ['memberList'] |
| ..children = content |
| ]; |
| } |
| |
| List<Element> _createTagSelect() { |
| var values = M.SampleProfileTag.values; |
| if (!_profileVM) { |
| values = const [ |
| M.SampleProfileTag.userOnly, |
| M.SampleProfileTag.vmOnly, |
| M.SampleProfileTag.none |
| ]; |
| } |
| var s; |
| return [ |
| s = new SelectElement() |
| ..classes = ['tag-select'] |
| ..value = tagToString(_tag) |
| ..children = values.map((tag) { |
| return new OptionElement( |
| value: tagToString(tag), selected: _tag == tag) |
| ..text = tagToString(tag); |
| }).toList(growable: false) |
| ..onChange.listen((_) { |
| _tag = values[s.selectedIndex]; |
| }) |
| ..onChange.map(_toEvent).listen(_triggerModeChange), |
| ]; |
| } |
| |
| static String tagToString(M.SampleProfileTag tag) { |
| switch (tag) { |
| case M.SampleProfileTag.userVM: |
| return 'User > VM'; |
| case M.SampleProfileTag.userOnly: |
| return 'User'; |
| case M.SampleProfileTag.vmUser: |
| return 'VM > User'; |
| case M.SampleProfileTag.vmOnly: |
| return 'VM'; |
| case M.SampleProfileTag.none: |
| return 'None'; |
| } |
| throw new Exception('Unknown tagToString'); |
| } |
| |
| SampleBufferControlChangedElement _toEvent(_) { |
| return new SampleBufferControlChangedElement(this); |
| } |
| |
| void _enableProfiler() { |
| _vm.enableProfiler().then((_) { |
| _triggerModeChange(_toEvent(null)); |
| }); |
| } |
| |
| void _triggerModeChange(e) => _onTagChange.add(e); |
| } |