| // 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:js_interop'; |
| |
| import 'package:web/web.dart'; |
| |
| import 'package:observatory/models.dart' as M; |
| import 'package:observatory/src/elements/helpers/custom_element.dart'; |
| import 'package:observatory/src/elements/helpers/element_utils.dart'; |
| import 'package:observatory/src/elements/helpers/rendering_scheduler.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}) { |
| 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 = <HTMLElement>[ |
| new HTMLHeadingElement.h2()..textContent = 'Sample buffer', |
| new HTMLHRElement() |
| ]; |
| 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 = <HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'content-centered-big' |
| ..appendChildren(content) |
| ]; |
| } |
| |
| static List<HTMLElement> _createStatusMessage(String message, |
| {double progress = 0.0}) { |
| return [ |
| new HTMLDivElement() |
| ..className = 'statusBox shadow center' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'statusMessage' |
| ..textContent = message, |
| new HTMLDivElement() |
| ..style.background = '#0489c3' |
| ..style.width = '$progress%' |
| ..style.height = '15px' |
| ..style.borderRadius = '4px' |
| ]) |
| ]; |
| } |
| |
| List<HTMLElement> _createDisabledMessage() { |
| return [ |
| new HTMLDivElement() |
| ..className = 'statusBox' 'shadow' 'center' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..appendChildren(<HTMLElement>[ |
| new HTMLHeadingElement.h1() |
| ..textContent = 'Profiling is disabled', |
| new HTMLBRElement(), |
| new HTMLDivElement() |
| ..innerHTML = 'Perhaps the <b>profile</b> ' |
| 'flag has been disabled for this VM.' |
| .toJS, |
| new HTMLBRElement(), |
| new HTMLButtonElement() |
| ..textContent = 'Enable profiler' |
| ..onClick.listen((_) { |
| _enableProfiler(); |
| }) |
| ]) |
| ]) |
| ]; |
| } |
| |
| List<HTMLElement> _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 = <HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'Refreshed at', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..textContent = |
| '$refreshT (fetched in ${fetchT}s) (loaded in ${loadT}s)' |
| ]), |
| new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'Profile contains ', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..textContent = '$sampleCount samples (spanning $timeSpan)' |
| ]), |
| new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'Sampling', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..textContent = '$maxStackDepth stack frames @ ${sampleRate}Hz' |
| ]), |
| ]; |
| if (_showTag) { |
| content.add(new HTMLDivElement() |
| ..className = 'memberItem' |
| ..appendChildren(<HTMLElement>[ |
| new HTMLDivElement() |
| ..className = 'memberName' |
| ..textContent = 'Tag Order', |
| new HTMLDivElement() |
| ..className = 'memberValue' |
| ..appendChildren(_createTagSelect()) |
| ])); |
| } |
| return [ |
| new HTMLDivElement() |
| ..className = 'memberList' |
| ..appendChildren(content) |
| ]; |
| } |
| |
| List<HTMLElement> _createTagSelect() { |
| var values = M.SampleProfileTag.values; |
| if (!_profileVM) { |
| values = const [ |
| M.SampleProfileTag.userOnly, |
| M.SampleProfileTag.vmOnly, |
| M.SampleProfileTag.none |
| ]; |
| } |
| final s = HTMLSelectElement() |
| ..className = 'tag-select' |
| ..value = tagToString(_tag) |
| ..appendChildren(values.map((tag) => HTMLOptionElement() |
| ..value = tagToString(tag) |
| ..selected = _tag == tag |
| ..textContent = tagToString(tag))); |
| s |
| ..onChange.listen((_) { |
| _tag = values[s.selectedIndex]; |
| }) |
| ..onChange.map(_toEvent).listen(_triggerModeChange); |
| return [s]; |
| } |
| |
| 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'; |
| } |
| } |
| |
| SampleBufferControlChangedElement _toEvent(_) { |
| return new SampleBufferControlChangedElement(this); |
| } |
| |
| void _enableProfiler() { |
| _vm.enableProfiler().then((_) { |
| _triggerModeChange(_toEvent(null)); |
| }); |
| } |
| |
| void _triggerModeChange(e) => _onTagChange.add(e); |
| } |