Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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:math' as math;

import 'package:collection/collection.dart';
Expand Down Expand Up @@ -30,8 +31,10 @@ class FlutterFramesController extends PerformanceFeatureController {
/// Whether we should show the Flutter frames chart.
ValueListenable<bool> get showFlutterFramesChart => _showFlutterFramesChart;
final _showFlutterFramesChart = ValueNotifier<bool>(true);
void toggleShowFlutterFrames(bool value) =>
_showFlutterFramesChart.value = value;
void toggleShowFlutterFrames(bool value) {
_showFlutterFramesChart.value = value;
unawaited(setIsActiveFeature(_showFlutterFramesChart.value));
}

/// Whether flutter frames are currently being recorded.
ValueListenable<bool> get recordingFrames => _recordingFrames;
Expand Down Expand Up @@ -82,6 +85,17 @@ class FlutterFramesController extends PerformanceFeatureController {
data?.displayRefreshRate = _displayRefreshRate.value;
}
}
await setIsActiveFeature(true);
}

// We override this for [FlutterFramesController] because this feature's
// "active" state will be determined by different parameters from other
// feature controllers, which respond to tab switches.
@override
Future<void> setIsActiveFeature(bool value) async {
final isFlutterApp = serviceManager.connectedApp?.isFlutterAppNow ?? false;
value = isFlutterApp && _showFlutterFramesChart.value;
await super.setIsActiveFeature(value);
}

void addFrame(FlutterFrame frame) {
Expand Down Expand Up @@ -128,7 +142,11 @@ class FlutterFramesController extends PerformanceFeatureController {

Future<void> toggleSelectedFrame(FlutterFrame frame) async {
handleSelectedFrame(frame);
performanceController.timelineEventsController.handleSelectedFrame(frame);
// We do not need to block the UI on the TimelineEvents feature loading the
// selected frame.
unawaited(
performanceController.timelineEventsController.handleSelectedFrame(frame),
);
}

void _addPendingFlutterFrames() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@

import '../../../../../primitives/trace_event.dart';
import '../../../../../primitives/utils.dart';
import '../../../performance_controller.dart';

class PerfettoController {
PerfettoController(this.performanceController);

final PerformanceController performanceController;

void init() {}

void dispose() {}

Future<void> onBecomingActive() async {}

Future<void> loadTrace(List<TraceEventWrapper> devToolsTraceEvents) async {}

Future<void> scrollToTimeRange(TimeRange timeRange) async {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '../../../../../primitives/auto_dispose.dart';
import '../../../../../primitives/trace_event.dart';
import '../../../../../primitives/utils.dart';
import '../../../../../shared/globals.dart';
import '../../../performance_controller.dart';

/// Flag to enable embedding an instance of the Perfetto UI running on
/// localhost.
Expand All @@ -24,6 +25,10 @@ const _debugUseLocalPerfetto = false;

class PerfettoController extends DisposableController
with AutoDisposeControllerMixin {
PerfettoController(this.performanceController);

final PerformanceController performanceController;

static const viewId = 'embedded-perfetto';

/// Url when running Perfetto locally following the instructions here:
Expand Down Expand Up @@ -82,6 +87,19 @@ class PerfettoController extends DisposableController

late final Completer<void> _devtoolsThemeHandlerReady;

/// Trace events that we should load, but have not yet since the trace viewer
/// is not visible (i.e. [TimelineEventsController.isActiveFeature] is false).
List<TraceEventWrapper>? pendingTraceEventsToLoad;

/// Time range we should scroll to, but have not yet since the trace viewer
/// is not visible (i.e. [TimelineEventsController.isActiveFeature] is false).
TimeRange? pendingScrollToTimeRange;

/// Boolean value representing the pending theme change we that we should
/// apply, but have not yet since the trace viewer is not visible (i.e.
/// [TimelineEventsController.isActiveFeature] is false).
bool? pendingLoadDarkMode;

void init() {
_perfettoReady = Completer();
_devtoolsThemeHandlerReady = Completer();
Expand All @@ -105,14 +123,34 @@ class PerfettoController extends DisposableController
html.window.addEventListener('message', _handleMessage);

if (isExternalBuild) {
unawaited(_loadInitialStyle());
unawaited(_loadStyle(preferences.darkModeTheme.value));
addAutoDisposeListener(preferences.darkModeTheme, () async {
_loadStyle(preferences.darkModeTheme.value);
await _loadStyle(preferences.darkModeTheme.value);
});
}
}

Future<void> onBecomingActive() async {
if (pendingLoadDarkMode != null) {
await _loadStyle(pendingLoadDarkMode!);
}
if (pendingTraceEventsToLoad != null) {
await loadTrace(pendingTraceEventsToLoad!);
pendingTraceEventsToLoad = null;
}
if (pendingScrollToTimeRange != null) {
await scrollToTimeRange(pendingScrollToTimeRange!);
pendingScrollToTimeRange = null;
}
}

Future<void> loadTrace(List<TraceEventWrapper> devToolsTraceEvents) async {
if (!performanceController.timelineEventsController.isActiveFeature) {
pendingTraceEventsToLoad = List.from(devToolsTraceEvents);
return;
}
pendingTraceEventsToLoad = null;

await _pingPerfettoUntilReady();

final encodedJson = jsonEncode({
Expand All @@ -132,6 +170,12 @@ class PerfettoController extends DisposableController
}

Future<void> scrollToTimeRange(TimeRange timeRange) async {
if (!performanceController.timelineEventsController.isActiveFeature) {
pendingScrollToTimeRange = timeRange;
return;
}
pendingScrollToTimeRange = null;

if (!timeRange.isWellFormed) {
notificationService.push(
'No timeline events available for the selected frame. Timeline '
Expand All @@ -152,16 +196,17 @@ class PerfettoController extends DisposableController
});
}

Future<void> _loadInitialStyle() async {
Future<void> _loadStyle(bool darkMode) async {
if (!isExternalBuild) return;
await _pingDevToolsThemeHandlerUntilReady();
_loadStyle(preferences.darkModeTheme.value);
}
if (!performanceController.timelineEventsController.isActiveFeature) {
pendingLoadDarkMode = darkMode;
return;
}
pendingLoadDarkMode = null;

void _loadStyle(bool darkMode) {
if (!isExternalBuild) return;
// This message will be handled by [devtools_theme_handler.js], which is
// included in the Perfetto build inside [packages/perfetto_compiled/dist].
await _pingDevToolsThemeHandlerUntilReady();
_postMessageWithId(
_devtoolsThemeChange,
perfettoIgnore: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import '_perfetto_controller_desktop.dart'
if (dart.library.html) '_perfetto_controller_web.dart';
import '_perfetto_desktop.dart' if (dart.library.html) '_perfetto_web.dart';

PerfettoController createPerfettoController() => PerfettoController();

class EmbeddedPerfetto extends StatelessWidget {
const EmbeddedPerfetto({Key? key, required this.perfettoController})
: super(key: key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import '../../performance_screen.dart';
import '../../performance_utils.dart';
import '../../simple_trace_example.dart';
import '../flutter_frames/flutter_frame_model.dart';
import 'perfetto/perfetto.dart';
import 'perfetto/_perfetto_controller_desktop.dart'
if (dart.library.html) 'perfetto/_perfetto_controller_web.dart';
import 'timeline_event_processor.dart';

/// Debugging flag to load sample trace events from [simple_trace_example.dart].
Expand All @@ -39,14 +40,18 @@ class TimelineEventsController extends PerformanceFeatureController
with AutoDisposeControllerMixin {
TimelineEventsController(super.performanceController) {
legacyController = LegacyTimelineEventsController(performanceController);
perfettoController = PerfettoController(performanceController);
}

/// Controller that contains business logic for the legacy trace viewer.
///
/// This controller will be used when [useLegacyTraceViewer.value] is true.
late final LegacyTimelineEventsController legacyController;

final perfettoController = createPerfettoController();
/// Controller that contains business logic for the Perfetto trace viewer.
///
/// This controller will be used when [useLegacyTraceViewer.value] is false.
late final PerfettoController perfettoController;

/// Trace events in the current timeline.
///
Expand All @@ -65,6 +70,9 @@ class TimelineEventsController extends PerformanceFeatureController
final useLegacyTraceViewer =
ValueNotifier<bool>(!FeatureFlags.embeddedPerfetto || !kIsWeb);

bool get _perfettoMode =>
FeatureFlags.embeddedPerfetto && !useLegacyTraceViewer.value;

/// Whether the recorded timeline data is currently being processed.
ValueListenable<bool> get processing => _processing;
final _processing = ValueNotifier<bool>(false);
Expand Down Expand Up @@ -108,6 +116,14 @@ class TimelineEventsController extends PerformanceFeatureController
}
}

@override
Future<void> onBecomingActive() async {
if (_perfettoMode) {
await perfettoController.onBecomingActive();
}
await super.onBecomingActive();
}

Future<void> _initForServiceConnection() async {
legacyController.init();
await serviceManager.timelineStreamManager.setDefaultTimelineStreams();
Expand All @@ -123,9 +139,7 @@ class TimelineEventsController extends PerformanceFeatureController
// Load available timeline events.
await _pullTraceEventsFromVmTimeline(isInitialPull: true);

_processing.value = true;
await processTraceEvents(allTraceEvents);
_processing.value = false;
await processAllTraceEvents();

_timelinePollingRateLimiter = RateLimiter(
_timelinePollingRateLimit,
Expand Down Expand Up @@ -243,13 +257,26 @@ class TimelineEventsController extends PerformanceFeatureController
}
}

FutureOr<void> processAvailableEvents() async {
FutureOr<void> processAllTraceEvents() async {
assert(!_processing.value);
_processing.value = true;
await processTraceEvents(allTraceEvents);
await _processAllTraceEvents();
_processing.value = false;
}

FutureOr<void> _processAllTraceEvents() async {
if (_perfettoMode) {
// TODO(kenz): hook up Perfetto event processor to process events before
// loading.
await perfettoController.loadTrace(allTraceEvents);
} else {
await legacyController.processTraceEvents(
allTraceEvents,
threadNamesById: threadNamesById,
);
}
}

Future<void> selectTimelineEvent(
TimelineEvent? event, {
bool updateProfiler = true,
Expand All @@ -265,19 +292,8 @@ class TimelineEventsController extends PerformanceFeatureController
}
}

FutureOr<void> processTraceEvents(List<TraceEventWrapper> traceEvents) async {
if (FeatureFlags.embeddedPerfetto && !useLegacyTraceViewer.value) {
await perfettoController.loadTrace(traceEvents);
} else {
await legacyController.processTraceEvents(
traceEvents,
threadNamesById: threadNamesById,
);
}
}

@override
void handleSelectedFrame(FlutterFrame frame) async {
Future<void> handleSelectedFrame(FlutterFrame frame) async {
if (useLegacyTraceViewer.value) {
await _legacySelectFrame(frame);
} else if (FeatureFlags.embeddedPerfetto) {
Expand Down Expand Up @@ -311,7 +327,7 @@ class TimelineEventsController extends PerformanceFeatureController
// Only try to pull timeline events for frames that are after the first
// well formed frame. Timeline events that occurred before this frame will
// have already fallen out of the buffer.
await processAvailableEvents();
await processAllTraceEvents();
}

if (framesController.currentFrameBeingSelected != frame) return;
Expand All @@ -324,9 +340,9 @@ class TimelineEventsController extends PerformanceFeatureController
_processing.value = true;
await Future.delayed(_timelinePollingInterval, () async {
if (framesController.currentFrameBeingSelected != frame) return;
await processTraceEvents(allTraceEvents);
_processing.value = false;
await _processAllTraceEvents();
});
_processing.value = false;
}

if (framesController.currentFrameBeingSelected != frame) return;
Expand Down Expand Up @@ -421,11 +437,9 @@ class TimelineEventsController extends PerformanceFeatureController
_httpTimelineLoggingEnabled.value = state;
}

void toggleUseLegacyTraceViewer(bool? value) {
Future<void> toggleUseLegacyTraceViewer(bool? value) async {
useLegacyTraceViewer.value = value ?? false;
// `unawaited` does not work for FutureOr
// ignore: discarded_futures
processAvailableEvents();
await processAllTraceEvents();
}

void recordTrace(Map<String, dynamic> trace) {
Expand Down Expand Up @@ -469,7 +483,7 @@ class TimelineEventsController extends PerformanceFeatureController
..clear()
..addAll(traceEvents);
_primeThreadIds(traceEvents);
await processAvailableEvents();
await processAllTraceEvents();

await legacyController.setOfflineData(offlineData);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,13 @@ class PerformanceController extends DisposableController
await Future.wait(futures);
}

// TODO(kenz): use this on tab switches to delay each feature handling a frame
// selection until they need to.
void setActiveFeature(PerformanceFeatureController featureController) {
_applyToFeatureControllers(
(c) => c.isActiveFeature = c == featureController,
Future<void> setActiveFeature(
PerformanceFeatureController? featureController,
) async {
await _applyToFeatureControllersAsync(
(c) async => await c.setIsActiveFeature(
featureController != null && c == featureController,
),
);
}

Expand Down Expand Up @@ -195,7 +197,18 @@ abstract class PerformanceFeatureController extends DisposableController {

PerformanceData? get data => performanceController.data;

bool isActiveFeature = false;
/// Whether this feature is active and visible to the user.
bool get isActiveFeature => _isActiveFeature;
Comment thread
kenzieschmoll marked this conversation as resolved.
bool _isActiveFeature = false;

Future<void> setIsActiveFeature(bool value) async {
Comment thread
kenzieschmoll marked this conversation as resolved.
_isActiveFeature = value;
if (value) {
await onBecomingActive();
}
}

Future<void> onBecomingActive() async {}

Future<void> init() async {}

Expand Down
Loading