[flutter_driver] make timeline request in chunks (#58430)
Work-around large timeline data killing devicelab machines by requesting data in 1 second intervals and combining at the end. Non-breaking change to the driver API.
diff --git a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart
index 10859f5..9ce1509 100644
--- a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart
+++ b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart
@@ -370,6 +370,10 @@
: const <Map<String, dynamic>>[];
}
+ Future<Map<String, Object>> _getVMTimelineMicros() async {
+ return await _peer.sendRequest('getVMTimelineMicros') as Map<String, dynamic>;
+ }
+
@override
Future<void> startTracing({
List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
@@ -397,15 +401,43 @@
@override
Future<Timeline> stopTracingAndDownloadTimeline({
Duration timeout = kUnusuallyLongTimeout,
+ int startTime,
+ int endTime,
}) async {
assert(timeout != null);
+ assert((startTime == null && endTime == null) ||
+ (startTime != null && endTime != null));
+
try {
await _warnIfSlow<void>(
future: _peer.sendRequest(_setVMTimelineFlagsMethodName, <String, String>{'recordedStreams': '[]'}),
timeout: timeout,
message: 'VM is taking an unusually long time to respond to being told to stop tracing...',
);
- return Timeline.fromJson(await _peer.sendRequest(_getVMTimelineMethodName) as Map<String, dynamic>);
+ if (startTime == null) {
+ return Timeline.fromJson(await _peer.sendRequest(_getVMTimelineMethodName) as Map<String, dynamic>);
+ }
+ const int kSecondInMicros = 1000000;
+ int currentStart = startTime;
+ int currentEnd = startTime + kSecondInMicros; // 1 second of timeline
+ final List<Map<String, Object>> chunks = <Map<String, Object>>[];
+ do {
+ final Map<String, Object> chunk = await _peer.sendRequest(_getVMTimelineMethodName, <String, Object>{
+ 'timeOriginMicros': currentStart,
+ // The range is inclusive, avoid double counting on the chance something
+ // aligns on the boundary.
+ 'timeExtentMicros': kSecondInMicros - 1,
+ }) as Map<String, dynamic>;
+ chunks.add(chunk);
+ currentStart = currentEnd;
+ currentEnd += kSecondInMicros;
+ } while (currentStart < endTime);
+ return Timeline.fromJson(<String, Object>{
+ 'traceEvents': <Object> [
+ for (Map<String, Object> chunk in chunks)
+ ...chunk['traceEvents'] as List<Object>,
+ ],
+ });
} catch (error, stackTrace) {
throw DriverError(
'Failed to stop tracing due to remote error',
@@ -434,13 +466,19 @@
if (!retainPriorEvents) {
await clearTimeline();
}
+ final Map<String, Object> startTimestamp = await _getVMTimelineMicros();
await startTracing(streams: streams);
await action();
+ final Map<String, Object> endTimestamp = await _getVMTimelineMicros();
if (!(await _isPrecompiledMode())) {
_log(_kDebugWarning);
}
- return stopTracingAndDownloadTimeline();
+
+ return stopTracingAndDownloadTimeline(
+ startTime: startTimestamp['timestamp'] as int,
+ endTime: endTimestamp['timestamp'] as int,
+ );
}
@override
diff --git a/packages/flutter_driver/test/flutter_driver_test.dart b/packages/flutter_driver/test/flutter_driver_test.dart
index b0d66d4..2809d24 100644
--- a/packages/flutter_driver/test/flutter_driver_test.dart
+++ b/packages/flutter_driver/test/flutter_driver_test.dart
@@ -538,6 +538,12 @@
return null;
});
+ when(mockPeer.sendRequest('getVMTimelineMicros'))
+ .thenAnswer((Invocation invocation) async {
+ log.add('getVMTimelineMicros');
+ return <String, Object>{};
+ });
+
when(mockPeer.sendRequest('setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[all]'}))))
.thenAnswer((Invocation invocation) async {
log.add('startTracing');
@@ -568,8 +574,10 @@
}, retainPriorEvents: true);
expect(log, const <String>[
+ 'getVMTimelineMicros',
'startTracing',
'action',
+ 'getVMTimelineMicros',
'stopTracing',
'download',
]);
@@ -583,13 +591,77 @@
expect(log, const <String>[
'clear',
+ 'getVMTimelineMicros',
'startTracing',
'action',
+ 'getVMTimelineMicros',
'stopTracing',
'download',
]);
expect(timeline.events.single.name, 'test event');
});
+
+ test('with time interval', () async {
+ int count = 0;
+ when(mockPeer.sendRequest('getVMTimelineMicros'))
+ .thenAnswer((Invocation invocation) async {
+ log.add('getVMTimelineMicros');
+ return <String, Object>{
+ if (count++ == 0)
+ 'timestamp': 0
+ else
+ 'timestamp': 1000001,
+ };
+ });
+ when(mockPeer.sendRequest('getVMTimeline', argThat(equals(<String, dynamic>{
+ 'timeOriginMicros': 0,
+ 'timeExtentMicros': 999999
+ }))))
+ .thenAnswer((Invocation invocation) async {
+ log.add('download 1');
+ return <String, dynamic>{
+ 'traceEvents': <dynamic>[
+ <String, String>{
+ 'name': 'test event 1',
+ },
+ ],
+ };
+ });
+ when(mockPeer.sendRequest('getVMTimeline', argThat(equals(<String, dynamic>{
+ 'timeOriginMicros': 1000000,
+ 'timeExtentMicros': 999999,
+ }))))
+ .thenAnswer((Invocation invocation) async {
+ log.add('download 2');
+ return <String, dynamic>{
+ 'traceEvents': <dynamic>[
+ <String, String>{
+ 'name': 'test event 2',
+ },
+ ],
+ };
+ });
+
+
+ final Timeline timeline = await driver.traceAction(() async {
+ log.add('action');
+ });
+
+ expect(log, const <String>[
+ 'clear',
+ 'getVMTimelineMicros',
+ 'startTracing',
+ 'action',
+ 'getVMTimelineMicros',
+ 'stopTracing',
+ 'download 1',
+ 'download 2',
+ ]);
+ expect(timeline.events.map((TimelineEvent event) => event.name), <String>[
+ 'test event 1',
+ 'test event 2',
+ ]);
+ });
});
group('traceAction with timeline streams', () {
@@ -598,6 +670,12 @@
bool startTracingCalled = false;
bool stopTracingCalled = false;
+ when(mockPeer.sendRequest('getVMTimelineMicros'))
+ .thenAnswer((Invocation invocation) async {
+ log.add('getVMTimelineMicros');
+ return <String, Object>{};
+ });
+
when(mockPeer.sendRequest('setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[Dart, GC, Compiler]'}))))
.thenAnswer((Invocation invocation) async {
startTracingCalled = true;