Skip to content

Commit 4fc0e03

Browse files
committed
Merge branch 'master' of github.com:flutter/devtools
2 parents 4cb6794 + 2d7357c commit 4fc0e03

20 files changed

+560
-273
lines changed

packages/devtools_app/lib/src/screens/memory/panes/chart/chart_control_pane.dart

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,15 @@ class _ChartControlPaneState extends State<ChartControlPane>
9090
children: [
9191
ValueListenableBuilder<bool>(
9292
valueListenable: controller.paused,
93-
builder: (context, paused, _) => Row(
94-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
95-
children: [
96-
PauseButton(
97-
iconOnly: true,
98-
onPressed: paused ? null : _onPause,
99-
tooltip: ChartPaneTooltips.pauseTooltip,
100-
),
101-
const SizedBox(width: denseSpacing),
102-
ResumeButton(
103-
iconOnly: true,
104-
onPressed: paused ? _onResume : null,
105-
tooltip: ChartPaneTooltips.resumeTooltip,
106-
),
107-
],
108-
),
93+
builder: (context, paused, _) {
94+
return PauseResumeButtonGroup(
95+
paused: paused,
96+
onPause: _onPause,
97+
onResume: _onResume,
98+
pauseTooltip: ChartPaneTooltips.pauseTooltip,
99+
resumeTooltip: ChartPaneTooltips.resumeTooltip,
100+
);
101+
},
109102
),
110103
const SizedBox(height: denseSpacing),
111104
IconLabelButton(

packages/devtools_app/lib/src/screens/memory/panes/chart/chart_pane.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ class _MemoryChartPaneState extends State<MemoryChartPane>
197197
return ValueListenableBuilder<bool>(
198198
valueListenable: preferences.memory.showChart,
199199
builder: (_, showChart, __) {
200-
// TODO(polina-c): animate the showing / hiding of this chart.
201-
200+
// TODO(https://github.com/flutter/devtools/issues/4576): animate
201+
// showing and hiding the chart.
202202
if (!showChart) return const SizedBox.shrink();
203203

204204
return RawKeyboardListener(

packages/devtools_app/lib/src/screens/memory/panes/control/primary_controls.dart

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ class PrimaryControls extends StatelessWidget {
4444
return Row(
4545
mainAxisSize: MainAxisSize.min,
4646
children: [
47-
const _ChartButton(),
47+
ChartVisibilityButton(
48+
showChart: preferences.memory.showChart,
49+
onPressed: (show) => preferences.memory.showChart.value = show,
50+
minScreenWidthForTextBeforeScaling: primaryControlsMinVerboseWidth,
51+
),
4852
const SizedBox(width: defaultSpacing),
4953
ClearButton(
5054
onPressed: controller.memorySource == MemoryController.liveFeed
@@ -57,23 +61,3 @@ class PrimaryControls extends StatelessWidget {
5761
);
5862
}
5963
}
60-
61-
class _ChartButton extends StatelessWidget {
62-
const _ChartButton({Key? key}) : super(key: key);
63-
64-
@override
65-
Widget build(BuildContext context) {
66-
return ValueListenableBuilder<bool>(
67-
valueListenable: preferences.memory.showChart,
68-
builder: (_, showChart, __) => IconLabelButton(
69-
key: key,
70-
tooltip: showChart ? 'Hide chart' : 'Show chart',
71-
icon: showChart ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
72-
label: 'Chart',
73-
minScreenWidthForTextBeforeScaling: primaryControlsMinVerboseWidth,
74-
onPressed: () => preferences.memory.showChart.value =
75-
!preferences.memory.showChart.value,
76-
),
77-
);
78-
}
79-
}

packages/devtools_app/lib/src/screens/performance/flutter_frames_chart.dart

Lines changed: 102 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,18 @@ import 'performance_utils.dart';
2929
bool debugFrames = false;
3030

3131
class FlutterFramesChart extends StatefulWidget {
32-
const FlutterFramesChart(
33-
this.frames,
34-
this.displayRefreshRate,
35-
);
36-
37-
static const chartLegendKey = Key('Flutter frames chart legend');
32+
const FlutterFramesChart({
33+
required this.frames,
34+
required this.displayRefreshRate,
35+
required this.isVisible,
36+
});
3837

3938
final List<FlutterFrame> frames;
4039

4140
final double displayRefreshRate;
4241

42+
final bool isVisible;
43+
4344
@override
4445
_FlutterFramesChartState createState() => _FlutterFramesChartState();
4546
}
@@ -135,6 +136,10 @@ class _FlutterFramesChartState extends State<FlutterFramesChart>
135136

136137
@override
137138
Widget build(BuildContext context) {
139+
// TODO(https://github.com/flutter/devtools/issues/4576): animate showing
140+
// and hiding the chart.
141+
if (!widget.isVisible) return const SizedBox.shrink();
142+
138143
return Container(
139144
margin: const EdgeInsets.only(
140145
left: denseSpacing,
@@ -150,28 +155,10 @@ class _FlutterFramesChartState extends State<FlutterFramesChart>
150155
const SizedBox(width: defaultSpacing),
151156
Padding(
152157
padding: EdgeInsets.only(bottom: _frameChartScrollbarOffset),
153-
child: Column(
154-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
155-
crossAxisAlignment: CrossAxisAlignment.start,
156-
children: [
157-
Legend(
158-
key: FlutterFramesChart.chartLegendKey,
159-
entries: [
160-
const LegendEntry('Frame Time (UI)', mainUiColor),
161-
const LegendEntry('Frame Time (Raster)', mainRasterColor),
162-
const LegendEntry('Jank (slow frame)', uiJankColor),
163-
LegendEntry(
164-
'Shader Compilation',
165-
shaderCompilationColor.background,
166-
),
167-
],
168-
),
169-
if (widget.frames.isNotEmpty)
170-
AverageFPS(
171-
frames: widget.frames,
172-
displayRefreshRate: widget.displayRefreshRate,
173-
),
174-
],
158+
child: FramesChartControls(
159+
controller: controller,
160+
frames: widget.frames,
161+
displayRefreshRate: widget.displayRefreshRate,
175162
),
176163
),
177164
],
@@ -245,6 +232,73 @@ class _FlutterFramesChartState extends State<FlutterFramesChart>
245232
}
246233
}
247234

235+
@visibleForTesting
236+
class FramesChartControls extends StatelessWidget {
237+
const FramesChartControls({
238+
required this.controller,
239+
required this.frames,
240+
required this.displayRefreshRate,
241+
});
242+
243+
static const _pauseTooltip = 'Pause Flutter frame recording';
244+
245+
static const _resumeTooltip = 'Resume Flutter frame recording';
246+
247+
final PerformanceController controller;
248+
249+
final List<FlutterFrame> frames;
250+
251+
final double displayRefreshRate;
252+
253+
@override
254+
Widget build(BuildContext context) {
255+
return Column(
256+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
257+
crossAxisAlignment: CrossAxisAlignment.start,
258+
children: [
259+
ValueListenableBuilder<bool>(
260+
valueListenable: controller.recordingFrames,
261+
builder: (context, recording, child) {
262+
return PauseResumeButtonGroup(
263+
paused: !recording,
264+
onPause: _pauseFrameRecording,
265+
onResume: _resumeFrameRecording,
266+
pauseTooltip: _pauseTooltip,
267+
resumeTooltip: _resumeTooltip,
268+
);
269+
},
270+
),
271+
Legend(
272+
dense: true,
273+
entries: [
274+
const LegendEntry('Frame Time (UI)', mainUiColor),
275+
const LegendEntry('Frame Time (Raster)', mainRasterColor),
276+
const LegendEntry('Jank (slow frame)', uiJankColor),
277+
LegendEntry(
278+
'Shader Compilation',
279+
shaderCompilationColor.background,
280+
),
281+
],
282+
),
283+
AverageFPS(
284+
frames: frames,
285+
displayRefreshRate: displayRefreshRate,
286+
),
287+
],
288+
);
289+
}
290+
291+
void _pauseFrameRecording() {
292+
ga.select(analytics_constants.performance, analytics_constants.pause);
293+
controller.toggleRecordingFrames(false);
294+
}
295+
296+
void _resumeFrameRecording() {
297+
ga.select(analytics_constants.performance, analytics_constants.resume);
298+
controller.toggleRecordingFrames(true);
299+
}
300+
}
301+
248302
class FlutterFramesChartItem extends StatelessWidget {
249303
const FlutterFramesChartItem({
250304
required this.index,
@@ -525,23 +579,30 @@ class AverageFPS extends StatelessWidget {
525579

526580
@override
527581
Widget build(BuildContext context) {
528-
final double sumFrameTimesMs = frames.fold(
529-
0.0,
530-
(sum, frame) =>
531-
sum +
532-
math.max(
533-
1000 / displayRefreshRate,
582+
late String fpsText;
583+
if (frames.isEmpty) {
584+
fpsText = '--';
585+
} else {
586+
final double sumFrameTimesMs = frames.fold(
587+
0.0,
588+
(sum, frame) =>
589+
sum +
534590
math.max(
535-
frame.buildTime.inMilliseconds,
536-
frame.rasterTime.inMilliseconds,
591+
1000 / displayRefreshRate,
592+
math.max(
593+
frame.buildTime.inMilliseconds,
594+
frame.rasterTime.inMilliseconds,
595+
),
537596
),
538-
),
539-
);
540-
final avgFrameTime = sumFrameTimesMs / frames.length;
541-
final avgFps = (1 / avgFrameTime * 1000).round();
597+
);
598+
final avgFrameTime = sumFrameTimesMs / frames.length;
599+
final avgFps = (1 / avgFrameTime * 1000).round();
600+
fpsText = '$avgFps';
601+
}
542602
return Text(
543-
'$avgFps FPS (average)',
603+
'$fpsText FPS (average)',
544604
maxLines: 2,
605+
style: Theme.of(context).legendTextStyle,
545606
);
546607
}
547608
}

packages/devtools_app/lib/src/screens/performance/panes/controls/performance_settings.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:flutter/material.dart';
66

7+
import '../../../../primitives/feature_flags.dart';
78
import '../../../../shared/common_widgets.dart';
89
import '../../../../shared/dialogs.dart';
910
import '../../../../shared/globals.dart';
@@ -137,6 +138,12 @@ class FlutterSettings extends StatelessWidget {
137138
notifier: controller.badgeTabForJankyFrames as ValueNotifier<bool?>,
138139
title: 'Badge Performance tab when Flutter UI jank is detected',
139140
),
141+
if (FeatureFlags.embeddedPerfetto)
142+
CheckboxSetting(
143+
notifier: controller.useLegacyTraceViewer,
144+
title: 'Use legacy trace viewer',
145+
onChanged: controller.toggleUseLegacyTraceViewer,
146+
),
140147
],
141148
);
142149
}

packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,28 @@ class PerfettoController extends DisposableController
133133
);
134134
}
135135

136-
void _postMessage(dynamic message) {
137-
_perfettoIFrame.contentWindow!.postMessage(
138-
message,
139-
_perfettoUrl,
140-
);
136+
void _postMessage(dynamic message) async {
137+
final callback = () {
138+
_perfettoIFrame.contentWindow!.postMessage(
139+
message,
140+
_perfettoUrl,
141+
);
142+
};
143+
if (_perfettoIFrame.contentWindow != null) {
144+
callback.call();
145+
} else {
146+
late StreamSubscription? onLoad;
147+
onLoad = _perfettoIFrame.onLoad.listen((event) {
148+
assert(
149+
_perfettoIFrame.contentWindow != null,
150+
'Something went wrong. The iFrame\'s contentWindow is null after the'
151+
' onLoad event.',
152+
);
153+
callback.call();
154+
onLoad?.cancel();
155+
onLoad = null;
156+
});
157+
}
141158
}
142159

143160
void _postMessageWithId(String id, {Map<String, dynamic> args = const {}}) {

packages/devtools_app/lib/src/screens/performance/performance_controller.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ class PerformanceController extends DisposableController
7272

7373
final perfettoController = createPerfettoController();
7474

75+
final useLegacyTraceViewer =
76+
ValueNotifier<bool>(!FeatureFlags.embeddedPerfetto || !kIsWeb);
77+
7578
final _exportController = ExportController();
7679

7780
/// The currently selected timeline event.
@@ -140,6 +143,12 @@ class PerformanceController extends DisposableController
140143
/// be processed and added to the timeline events flame chart.
141144
int _nextTimelineEventIndexToProcess = 0;
142145

146+
/// Whether we should show the Flutter frames chart.
147+
ValueListenable<bool> get showFlutterFramesChart => _showFlutterFramesChart;
148+
final _showFlutterFramesChart = ValueNotifier<bool>(true);
149+
void toggleShowFlutterFrames(bool value) =>
150+
_showFlutterFramesChart.value = value;
151+
143152
/// Whether flutter frames are currently being recorded.
144153
ValueListenable<bool> get recordingFrames => _recordingFrames;
145154
final _recordingFrames = ValueNotifier<bool>(true);
@@ -699,7 +708,7 @@ class PerformanceController extends DisposableController
699708
}
700709

701710
FutureOr<void> processTraceEvents(List<TraceEventWrapper> traceEvents) async {
702-
if (FeatureFlags.embeddedPerfetto) {
711+
if (FeatureFlags.embeddedPerfetto && !useLegacyTraceViewer.value) {
703712
await perfettoController.loadTrace(traceEvents);
704713
} else {
705714
await _processTraceEvents(traceEvents);
@@ -929,6 +938,11 @@ class PerformanceController extends DisposableController
929938
_httpTimelineLoggingEnabled.value = state;
930939
}
931940

941+
void toggleUseLegacyTraceViewer(bool? value) {
942+
useLegacyTraceViewer.value = value ?? false;
943+
processAvailableEvents();
944+
}
945+
932946
/// Clears the timeline data currently stored by the controller as well the
933947
/// VM timeline if a connected app is present.
934948
Future<void> clearData() async {

0 commit comments

Comments
 (0)