Skip to content

Commit ceb09b8

Browse files
authored
redux of a change to use new engine APIs for Flutter.Frame events (#34365)
1 parent dfecafa commit ceb09b8

3 files changed

Lines changed: 77 additions & 29 deletions

File tree

‎dev/devicelab/bin/tasks/service_extensions_test.dart‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ import 'dart:async';
66
import 'dart:convert';
77
import 'dart:io';
88

9-
import 'package:path/path.dart' as path;
10-
import 'package:vm_service_client/vm_service_client.dart';
11-
129
import 'package:flutter_devicelab/framework/adb.dart';
1310
import 'package:flutter_devicelab/framework/framework.dart';
1411
import 'package:flutter_devicelab/framework/utils.dart';
12+
import 'package:path/path.dart' as path;
13+
import 'package:vm_service_client/vm_service_client.dart';
1514

1615
void main() {
1716
task(() async {
@@ -57,10 +56,19 @@ void main() {
5756
final VMServiceClient client = VMServiceClient.connect('ws://localhost:$vmServicePort/ws');
5857
final VM vm = await client.getVM();
5958
final VMIsolateRef isolate = vm.isolates.first;
60-
final Stream<VMExtensionEvent> frameEvents = isolate.onExtensionEvent.where(
61-
(VMExtensionEvent e) => e.kind == 'Flutter.Frame');
62-
final Stream<VMExtensionEvent> navigationEvents = isolate.onExtensionEvent.where(
63-
(VMExtensionEvent e) => e.kind == 'Flutter.Navigation');
59+
60+
final StreamController<VMExtensionEvent> frameEventsController = StreamController<VMExtensionEvent>();
61+
final StreamController<VMExtensionEvent> navigationEventsController = StreamController<VMExtensionEvent>();
62+
isolate.onExtensionEvent.listen((VMExtensionEvent event) {
63+
if (event.kind == 'Flutter.Frame') {
64+
frameEventsController.add(event);
65+
} else if (event.kind == 'Flutter.Navigation') {
66+
navigationEventsController.add(event);
67+
}
68+
});
69+
70+
final Stream<VMExtensionEvent> frameEvents = frameEventsController.stream;
71+
final Stream<VMExtensionEvent> navigationEvents = navigationEventsController.stream;
6472

6573
print('reassembling app...');
6674
final Future<VMExtensionEvent> frameFuture = frameEvents.first;
@@ -71,13 +79,17 @@ void main() {
7179
print('${event.kind}: ${event.data}');
7280

7381
// validate the fields
74-
// {number: 8, startTime: 0, elapsed: 1437}
82+
// {number: 8, startTime: 0, elapsed: 1437, build: 600, raster: 800}
7583
expect(event.data['number'] is int);
7684
expect(event.data['number'] >= 0);
7785
expect(event.data['startTime'] is int);
7886
expect(event.data['startTime'] >= 0);
7987
expect(event.data['elapsed'] is int);
8088
expect(event.data['elapsed'] >= 0);
89+
expect(event.data['build'] is int);
90+
expect(event.data['build'] >= 0);
91+
expect(event.data['raster'] is int);
92+
expect(event.data['raster'] >= 0);
8193

8294
final Future<VMExtensionEvent> navigationFuture = navigationEvents.first;
8395
// This tap triggers a navigation event.

‎packages/flutter/lib/src/scheduler/binding.dart‎

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
import 'dart:async';
66
import 'dart:collection';
7-
import 'dart:developer';
8-
import 'dart:ui' show AppLifecycleState;
7+
import 'dart:developer' show Flow, Timeline;
8+
import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming;
99

1010
import 'package:collection/collection.dart' show PriorityQueue, HeapPriorityQueue;
1111
import 'package:flutter/foundation.dart';
@@ -198,6 +198,17 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
198198
window.onDrawFrame = _handleDrawFrame;
199199
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
200200
readInitialLifecycleStateFromNativeWindow();
201+
202+
if (!kReleaseMode) {
203+
int frameNumber = 0;
204+
205+
window.onReportTimings = (List<FrameTiming> timings) {
206+
for (FrameTiming frameTiming in timings) {
207+
frameNumber += 1;
208+
_profileFramePostEvent(frameNumber, frameTiming);
209+
}
210+
};
211+
}
201212
}
202213

203214
/// The current [SchedulerBinding], if one has been created.
@@ -842,8 +853,7 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
842853
}
843854
Duration _currentFrameTimeStamp;
844855

845-
int _profileFrameNumber = 0;
846-
final Stopwatch _profileFrameStopwatch = Stopwatch();
856+
int _debugFrameNumber = 0;
847857
String _debugBanner;
848858
bool _ignoreNextEngineDrawFrame = false;
849859

@@ -894,21 +904,17 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
894904
if (rawTimeStamp != null)
895905
_lastRawTimeStamp = rawTimeStamp;
896906

897-
if (!kReleaseMode) {
898-
_profileFrameNumber += 1;
899-
_profileFrameStopwatch.reset();
900-
_profileFrameStopwatch.start();
901-
}
902-
903907
assert(() {
908+
_debugFrameNumber += 1;
909+
904910
if (debugPrintBeginFrameBanner || debugPrintEndFrameBanner) {
905911
final StringBuffer frameTimeStampDescription = StringBuffer();
906912
if (rawTimeStamp != null) {
907913
_debugDescribeTimeStamp(_currentFrameTimeStamp, frameTimeStampDescription);
908914
} else {
909915
frameTimeStampDescription.write('(warm-up frame)');
910916
}
911-
_debugBanner = '▄▄▄▄▄▄▄▄ Frame ${_profileFrameNumber.toString().padRight(7)} ${frameTimeStampDescription.toString().padLeft(18)} ▄▄▄▄▄▄▄▄';
917+
_debugBanner = '▄▄▄▄▄▄▄▄ Frame ${_debugFrameNumber.toString().padRight(7)} ${frameTimeStampDescription.toString().padLeft(18)} ▄▄▄▄▄▄▄▄';
912918
if (debugPrintBeginFrameBanner)
913919
debugPrint(_debugBanner);
914920
}
@@ -961,10 +967,6 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
961967
} finally {
962968
_schedulerPhase = SchedulerPhase.idle;
963969
Timeline.finishSync(); // end the Frame
964-
if (!kReleaseMode) {
965-
_profileFrameStopwatch.stop();
966-
_profileFramePostEvent();
967-
}
968970
assert(() {
969971
if (debugPrintEndFrameBanner)
970972
debugPrint('▀' * _debugBanner.length);
@@ -975,11 +977,13 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
975977
}
976978
}
977979

978-
void _profileFramePostEvent() {
980+
void _profileFramePostEvent(int frameNumber, FrameTiming frameTiming) {
979981
postEvent('Flutter.Frame', <String, dynamic>{
980-
'number': _profileFrameNumber,
981-
'startTime': _currentFrameTimeStamp.inMicroseconds,
982-
'elapsed': _profileFrameStopwatch.elapsedMicroseconds,
982+
'number': frameNumber,
983+
'startTime': frameTiming.timestampInMicroseconds(FramePhase.buildStart),
984+
'elapsed': frameTiming.totalSpan.inMicroseconds,
985+
'build': frameTiming.buildDuration.inMicroseconds,
986+
'raster': frameTiming.rasterDuration.inMicroseconds,
983987
});
984988
}
985989

‎packages/flutter/test/scheduler/scheduler_test.dart‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,26 @@
33
// found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:ui' show window, FrameTiming;
67

78
import 'package:flutter/foundation.dart';
89
import 'package:flutter/scheduler.dart';
910
import 'package:flutter/services.dart';
1011

1112
import '../flutter_test_alternative.dart';
1213

13-
class TestSchedulerBinding extends BindingBase with ServicesBinding, SchedulerBinding { }
14+
class TestSchedulerBinding extends BindingBase with ServicesBinding, SchedulerBinding {
15+
final Map<String, List<Map<String, dynamic>>> eventsDispatched = <String, List<Map<String, dynamic>>>{};
16+
17+
@override
18+
void postEvent(String eventKind, Map<dynamic, dynamic> eventData) {
19+
getEventsDispatched(eventKind).add(eventData);
20+
}
21+
22+
List<Map<String, dynamic>> getEventsDispatched(String eventKind) {
23+
return eventsDispatched.putIfAbsent(eventKind, () => <Map<String, dynamic>>[]);
24+
}
25+
}
1426

1527
class TestStrategy {
1628
int allowedPriority = 10000;
@@ -21,7 +33,8 @@ class TestStrategy {
2133
}
2234

2335
void main() {
24-
SchedulerBinding scheduler;
36+
TestSchedulerBinding scheduler;
37+
2538
setUpAll(() {
2639
scheduler = TestSchedulerBinding();
2740
});
@@ -116,4 +129,23 @@ void main() {
116129
expect(timerQueueTasks.length, 2);
117130
expect(taskExecuted, false);
118131
});
132+
133+
test('Flutter.Frame event fired', () async {
134+
window.onReportTimings(<FrameTiming>[FrameTiming(<int>[
135+
// build start, build finish
136+
10000, 15000,
137+
// raster start, raster finish
138+
16000, 20000,
139+
])]);
140+
141+
final List<Map<String, dynamic>> events = scheduler.getEventsDispatched('Flutter.Frame');
142+
expect(events, hasLength(1));
143+
144+
final Map<String, dynamic> event = events.first;
145+
expect(event['number'], isNonNegative);
146+
expect(event['startTime'], 10000);
147+
expect(event['elapsed'], 10000);
148+
expect(event['build'], 5000);
149+
expect(event['raster'], 4000);
150+
});
119151
}

0 commit comments

Comments
 (0)