Performance screen cleanups (#4751)
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart b/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart
index e998cb4..e2f7cb7 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart
@@ -498,7 +498,7 @@
),
);
}
- unawaited(framesController.toggleSelectedFrame(frame));
+ framesController.handleSelectedFrame(frame);
}
}
@@ -516,7 +516,7 @@
final bool hasShaderJank;
- static const double _moreInfoLinkWidth = 85.0;
+ static const double _moreInfoLinkWidth = 100.0;
static const _textMeasurementBuffer = 4.0;
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_controller.dart b/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_controller.dart
index b613b04..a7e4936 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_controller.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_controller.dart
@@ -140,15 +140,6 @@
return _unassignedFlutterFrames.containsKey(frameNumber);
}
- Future<void> toggleSelectedFrame(FlutterFrame frame) async {
- 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() {
_pendingFlutterFrames.forEach(_maybeBadgeTabForJankyFrame);
data!.frames.addAll(_pendingFlutterFrames);
@@ -210,6 +201,12 @@
_data.selectedFrame = frame;
_selectedFrameNotifier.value = frame;
+
+ // We do not need to block the UI on the TimelineEvents feature loading the
+ // selected frame.
+ unawaited(
+ performanceController.timelineEventsController.handleSelectedFrame(frame),
+ );
}
@override
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/timeline_event_processor.dart b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/timeline_event_processor.dart
index 7afb8e6..9ebac6c 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/timeline_event_processor.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/timeline_event_processor.dart
@@ -42,7 +42,7 @@
/// Notifies with the current progress value of processing Timeline data.
///
/// This value should sit between 0.0 and 1.0.
- ValueListenable get progressNotifier => _progressNotifier;
+ ValueListenable<double> get progressNotifier => _progressNotifier;
final _progressNotifier = ValueNotifier<double>(0.0);
int _traceEventsProcessed = 0;
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/timeline_events_controller.dart b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/timeline_events_controller.dart
index 0cd511c..f08dd8d 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/timeline_events_controller.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/timeline_events_controller.dart
@@ -37,11 +37,25 @@
/// Debugging flag to load sample trace events from [simple_trace_example.dart].
bool debugSimpleTrace = false;
+enum EventsControllerStatus {
+ empty,
+ processing,
+ ready,
+}
+
class TimelineEventsController extends PerformanceFeatureController
with AutoDisposeControllerMixin {
TimelineEventsController(super.performanceController) {
legacyController = LegacyTimelineEventsController(performanceController);
perfettoController = PerfettoController(performanceController);
+ addAutoDisposeListener(_workTracker.active, () {
+ final active = _workTracker.active.value;
+ if (active) {
+ _status.value = EventsControllerStatus.processing;
+ } else {
+ _status.value = EventsControllerStatus.ready;
+ }
+ });
}
/// Controller that contains business logic for the legacy trace viewer.
@@ -75,7 +89,10 @@
FeatureFlags.embeddedPerfetto && !useLegacyTraceViewer.value;
/// Whether the recorded timeline data is currently being processed.
- ValueListenable<bool> get processing => _workTracker.active;
+ ValueListenable<EventsControllerStatus> get status => _status;
+ final _status =
+ ValueNotifier<EventsControllerStatus>(EventsControllerStatus.empty);
+
final _workTracker = FutureWorkTracker();
// TODO(jacobr): this isn't accurate. Another page of DevTools
@@ -251,10 +268,7 @@
);
}
- legacyController.processor.primeThreadIds(
- uiThreadId: uiThreadId,
- rasterThreadId: rasterThreadId,
- );
+ _primeThreadIds(uiThreadId: uiThreadId, rasterThreadId: rasterThreadId);
}
}
@@ -443,9 +457,10 @@
data!.traceEvents.add(trace);
}
- void _primeThreadIds(List<TraceEventWrapper> traceEvents) {
- final uiThreadId = _threadIdForEvents({uiEventName}, traceEvents);
- final rasterThreadId = _threadIdForEvents({rasterEventName}, traceEvents);
+ void _primeThreadIds({
+ required int? uiThreadId,
+ required int? rasterThreadId,
+ }) {
legacyController.processor.primeThreadIds(
uiThreadId: uiThreadId,
rasterThreadId: rasterThreadId,
@@ -479,10 +494,17 @@
allTraceEvents
..clear()
..addAll(traceEvents);
- _primeThreadIds(traceEvents);
+
+ final uiThreadId = _threadIdForEvents({uiEventName}, traceEvents);
+ final rasterThreadId = _threadIdForEvents({rasterEventName}, traceEvents);
+ _primeThreadIds(uiThreadId: uiThreadId, rasterThreadId: rasterThreadId);
await processAllTraceEvents();
await legacyController.setOfflineData(offlineData);
+
+ if (offlineData.selectedFrame != null && _perfettoMode) {
+ // TODO(kenz): scroll the perfetto viewer to the selected time range.
+ }
}
@override
@@ -493,6 +515,7 @@
threadNamesById.clear();
_workTracker.clear();
legacyController.clearData();
+ _status.value = EventsControllerStatus.empty;
if (FeatureFlags.embeddedPerfetto) {
await perfettoController.clear();
}
diff --git a/packages/devtools_app/lib/src/screens/performance/performance_model.dart b/packages/devtools_app/lib/src/screens/performance/performance_model.dart
index 1e9b548..f007687 100644
--- a/packages/devtools_app/lib/src/screens/performance/performance_model.dart
+++ b/packages/devtools_app/lib/src/screens/performance/performance_model.dart
@@ -5,6 +5,7 @@
import 'dart:collection';
import 'dart:math' as math;
+import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import '../../charts/flame_chart.dart';
@@ -333,6 +334,8 @@
final frames = framesJson
.map((Map<String, dynamic> f) => FlutterFrame.parse(f))
.toList();
+ final selectedFrame =
+ frames.firstWhereOrNull((frame) => frame.id == selectedFrameId);
final Map<String, dynamic> selectedEventJson =
json[PerformanceData.selectedEventKey] ?? {};
@@ -354,6 +357,7 @@
return OfflinePerformanceData._(
traceEvents: traceEvents,
+ selectedFrame: selectedFrame,
selectedFrameId: selectedFrameId,
frames: frames,
selectedEvent: selectedEvent,
diff --git a/packages/devtools_app/lib/src/screens/performance/performance_screen.dart b/packages/devtools_app/lib/src/screens/performance/performance_screen.dart
index 94d959c..139a463 100644
--- a/packages/devtools_app/lib/src/screens/performance/performance_screen.dart
+++ b/packages/devtools_app/lib/src/screens/performance/performance_screen.dart
@@ -26,7 +26,6 @@
import 'panes/controls/layer_debugging_options.dart';
import 'panes/controls/performance_settings.dart';
import 'panes/flutter_frames/flutter_frames_chart.dart';
-import 'panes/flutter_frames/flutter_frames_controller.dart';
import 'panes/timeline_events/timeline_events_controller.dart';
import 'performance_controller.dart';
import 'performance_model.dart';
@@ -66,14 +65,6 @@
AutoDisposeMixin,
OfflineScreenMixin<PerformanceScreenBody, OfflinePerformanceData>,
ProvidedControllerMixin<PerformanceController, PerformanceScreenBody> {
- bool processing = false;
-
- double processingProgress = 0.0;
-
- late TimelineEventsController _timelineEventsController;
-
- late FlutterFramesController _flutterFramesController;
-
@override
void initState() {
super.initState();
@@ -101,28 +92,9 @@
if (!initController()) return;
- _timelineEventsController = controller.timelineEventsController;
- _flutterFramesController = controller.flutterFramesController;
-
cancelListeners();
- processing = controller.timelineEventsController.processing.value;
- addAutoDisposeListener(controller.timelineEventsController.processing, () {
- setState(() {
- processing = controller.timelineEventsController.processing.value;
- });
- });
-
- final legacyProcessor =
- _timelineEventsController.legacyController.processor;
- processingProgress = legacyProcessor.progressNotifier.value;
- addAutoDisposeListener(legacyProcessor.progressNotifier, () {
- setState(() {
- processingProgress = legacyProcessor.progressNotifier.value;
- });
- });
-
- addAutoDisposeListener(_flutterFramesController.selectedFrame);
+ addAutoDisposeListener(controller.flutterFramesController.selectedFrame);
// Load offline timeline data if available.
if (shouldLoadOfflineData()) {
@@ -149,10 +121,6 @@
controller.offlinePerformanceData != null &&
controller.offlinePerformanceData!.frames.isNotEmpty;
- final tabbedPerformanceView = TabbedPerformanceView(
- processing: processing,
- processingProgress: processingProgress,
- );
final performanceScreen = Column(
children: [
if (!offlineController.offlineMode.value) _buildPerformanceControls(),
@@ -160,8 +128,8 @@
if (isOfflineFlutterApp ||
(!offlineController.offlineMode.value &&
serviceManager.connectedApp!.isFlutterAppNow!))
- FlutterFramesChart(_flutterFramesController),
- Expanded(child: tabbedPerformanceView),
+ FlutterFramesChart(controller.flutterFramesController),
+ const Expanded(child: TabbedPerformanceView()),
],
);
@@ -185,10 +153,15 @@
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- _PrimaryControls(
- controller: controller,
- processing: processing,
- onClear: () => setState(() {}),
+ ValueListenableBuilder<EventsControllerStatus>(
+ valueListenable: controller.timelineEventsController.status,
+ builder: (context, status, _) {
+ return _PrimaryControls(
+ controller: controller,
+ processing: status == EventsControllerStatus.processing,
+ onClear: () => setState(() {}),
+ );
+ },
),
const SizedBox(width: defaultSpacing),
SecondaryPerformanceControls(controller: controller),
diff --git a/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart b/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart
index 22f1f02..cc36913 100644
--- a/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart
+++ b/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart
@@ -31,14 +31,7 @@
final timelineSearchFieldKey = GlobalKey(debugLabel: 'TimelineSearchFieldKey');
class TabbedPerformanceView extends StatefulWidget {
- const TabbedPerformanceView({
- required this.processing,
- required this.processingProgress,
- });
-
- final bool processing;
-
- final double processingProgress;
+ const TabbedPerformanceView();
@override
_TabbedPerformanceViewState createState() => _TabbedPerformanceViewState();
@@ -90,7 +83,6 @@
showRebuildStats =
showRebuildStats && offlineData.rebuildCountModel.isNotEmpty;
}
-
final tabRecords = <_PerformanceTabRecord>[
if (showFrameAnalysis) _frameAnalysisRecord(),
if (showRasterStats) _rasterStatsRecord(),
@@ -219,9 +211,6 @@
}
_PerformanceTabRecord _timelineEventsRecord() {
- final data = controller.data;
- final hasData = data != null && !data.isEmpty;
- final searchFieldEnabled = hasData && !widget.processing;
return _PerformanceTabRecord(
tab: _buildTab(
tabName: 'Timeline Events',
@@ -232,7 +221,14 @@
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (useLegacy || !FeatureFlags.embeddedPerfetto) ...[
- _buildSearchField(searchFieldEnabled),
+ ValueListenableBuilder<EventsControllerStatus>(
+ valueListenable: _timelineEventsController.status,
+ builder: (context, status, _) {
+ final searchFieldEnabled =
+ status == EventsControllerStatus.ready;
+ return _buildSearchField(searchFieldEnabled);
+ },
+ ),
const FlameChartHelpButton(
gaScreen: PerformanceScreen.id,
gaSelection: analytics_constants.timelineFlameChartHelp,
@@ -252,10 +248,18 @@
builder: (context, useLegacy, _) {
return (useLegacy || !FeatureFlags.embeddedPerfetto)
? KeepAliveWrapper(
- child: TimelineEventsView(
- controller: _timelineEventsController,
- processing: widget.processing,
- processingProgress: widget.processingProgress,
+ child: DualValueListenableBuilder<EventsControllerStatus,
+ double>(
+ firstListenable: _timelineEventsController.status,
+ secondListenable: _timelineEventsController
+ .legacyController.processor.progressNotifier,
+ builder: (context, status, processingProgress, _) {
+ return TimelineEventsView(
+ controller: _timelineEventsController,
+ processing: status == EventsControllerStatus.processing,
+ processingProgress: processingProgress,
+ );
+ },
),
)
: KeepAliveWrapper(
diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml
index 514c52d..23778fe 100644
--- a/packages/devtools_app/pubspec.yaml
+++ b/packages/devtools_app/pubspec.yaml
@@ -18,6 +18,8 @@
# Pin ansicolor to version before pre-NNBD version 1.1.0, should be ^1.0.5
# See https://github.com/flutter/devtools/issues/2530
ansicolor: ^2.0.0
+ # TODO: https://github.com/flutter/devtools/issues/4728 - remove constraint when archive is fixed
+ archive: <3.3.3
async: ^2.0.0
codicon: ^1.0.0
collection: ^1.15.0
diff --git a/packages/devtools_app/test/performance/flutter_frames/flutter_frames_controller_test.dart b/packages/devtools_app/test/performance/flutter_frames/flutter_frames_controller_test.dart
index 0fa0614..54868ac 100644
--- a/packages/devtools_app/test/performance/flutter_frames/flutter_frames_controller_test.dart
+++ b/packages/devtools_app/test/performance/flutter_frames/flutter_frames_controller_test.dart
@@ -84,7 +84,7 @@
// Select a frame.
expect(framesController.selectedFrame.value, isNull);
- await framesController.toggleSelectedFrame(frame0);
+ framesController.handleSelectedFrame(frame0);
expect(
framesController.selectedFrame.value,
equals(frame0),
@@ -94,14 +94,14 @@
expect(timelineControllerHandlerCalled, isTrue);
// Unselect this frame.
- await framesController.toggleSelectedFrame(frame0);
+ framesController.handleSelectedFrame(frame0);
expect(
framesController.selectedFrame.value,
isNull,
);
// Select a different frame.
- await framesController.toggleSelectedFrame(frame1);
+ framesController.handleSelectedFrame(frame1);
expect(
framesController.selectedFrame.value,
equals(frame1),
diff --git a/packages/devtools_app/test/performance/tabbed_performance_view_test.dart b/packages/devtools_app/test/performance/tabbed_performance_view_test.dart
index 8404f93..f650a79 100644
--- a/packages/devtools_app/test/performance/tabbed_performance_view_test.dart
+++ b/packages/devtools_app/test/performance/tabbed_performance_view_test.dart
@@ -60,6 +60,11 @@
when(mockTimelineEventsController.data).thenReturn(controller.data);
when(mockTimelineEventsController.useLegacyTraceViewer)
.thenReturn(ValueNotifier<bool>(true));
+ when(mockTimelineEventsController.status).thenReturn(
+ const FixedValueListenable<EventsControllerStatus>(
+ EventsControllerStatus.ready,
+ ),
+ );
when(mockTimelineEventsController.legacyController)
.thenReturn(LegacyTimelineEventsController(controller));
when(controller.timelineEventsController)
@@ -90,10 +95,7 @@
await tester.pumpWidget(
wrapWithControllers(
- const TabbedPerformanceView(
- processing: false,
- processingProgress: 0.0,
- ),
+ const TabbedPerformanceView(),
performance: controller,
),
);
diff --git a/packages/devtools_app/test/performance/timeline_events/timeline_flame_chart_test.dart b/packages/devtools_app/test/performance/timeline_events/timeline_flame_chart_test.dart
index 147948a..8c59a63 100644
--- a/packages/devtools_app/test/performance/timeline_events/timeline_flame_chart_test.dart
+++ b/packages/devtools_app/test/performance/timeline_events/timeline_flame_chart_test.dart
@@ -164,8 +164,8 @@
.addTimelineEvent(goldenRasterTimelineEvent);
final data = controller.data!;
expect(data.frames.length, equals(1));
- await controller.flutterFramesController
- .toggleSelectedFrame(data.frames.first);
+ controller.flutterFramesController
+ .handleSelectedFrame(data.frames.first);
await tester.pumpAndSettle();
});
expect(find.byType(TimelineFlameChart), findsOneWidget);