diff --git a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart index 10859f55da5..9ce15093aa8 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 @@ class VMServiceFlutterDriver extends FlutterDriver { : const >[]; } + Future> _getVMTimelineMicros() async { + return await _peer.sendRequest('getVMTimelineMicros') as Map; + } + @override Future startTracing({ List streams = const [TimelineStream.all], @@ -397,15 +401,43 @@ class VMServiceFlutterDriver extends FlutterDriver { @override Future stopTracingAndDownloadTimeline({ Duration timeout = kUnusuallyLongTimeout, + int startTime, + int endTime, }) async { assert(timeout != null); + assert((startTime == null && endTime == null) || + (startTime != null && endTime != null)); + try { await _warnIfSlow( future: _peer.sendRequest(_setVMTimelineFlagsMethodName, {'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); + if (startTime == null) { + return Timeline.fromJson(await _peer.sendRequest(_getVMTimelineMethodName) as Map); + } + const int kSecondInMicros = 1000000; + int currentStart = startTime; + int currentEnd = startTime + kSecondInMicros; // 1 second of timeline + final List> chunks = >[]; + do { + final Map chunk = await _peer.sendRequest(_getVMTimelineMethodName, { + 'timeOriginMicros': currentStart, + // The range is inclusive, avoid double counting on the chance something + // aligns on the boundary. + 'timeExtentMicros': kSecondInMicros - 1, + }) as Map; + chunks.add(chunk); + currentStart = currentEnd; + currentEnd += kSecondInMicros; + } while (currentStart < endTime); + return Timeline.fromJson({ + 'traceEvents': [ + for (Map chunk in chunks) + ...chunk['traceEvents'] as List, + ], + }); } catch (error, stackTrace) { throw DriverError( 'Failed to stop tracing due to remote error', @@ -434,13 +466,19 @@ class VMServiceFlutterDriver extends FlutterDriver { if (!retainPriorEvents) { await clearTimeline(); } + final Map startTimestamp = await _getVMTimelineMicros(); await startTracing(streams: streams); await action(); + final Map 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 b0d66d442e7..2809d24a0d7 100644 --- a/packages/flutter_driver/test/flutter_driver_test.dart +++ b/packages/flutter_driver/test/flutter_driver_test.dart @@ -538,6 +538,12 @@ void main() { return null; }); + when(mockPeer.sendRequest('getVMTimelineMicros')) + .thenAnswer((Invocation invocation) async { + log.add('getVMTimelineMicros'); + return {}; + }); + when(mockPeer.sendRequest('setVMTimelineFlags', argThat(equals({'recordedStreams': '[all]'})))) .thenAnswer((Invocation invocation) async { log.add('startTracing'); @@ -568,8 +574,10 @@ void main() { }, retainPriorEvents: true); expect(log, const [ + 'getVMTimelineMicros', 'startTracing', 'action', + 'getVMTimelineMicros', 'stopTracing', 'download', ]); @@ -583,13 +591,77 @@ void main() { expect(log, const [ '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 { + if (count++ == 0) + 'timestamp': 0 + else + 'timestamp': 1000001, + }; + }); + when(mockPeer.sendRequest('getVMTimeline', argThat(equals({ + 'timeOriginMicros': 0, + 'timeExtentMicros': 999999 + })))) + .thenAnswer((Invocation invocation) async { + log.add('download 1'); + return { + 'traceEvents': [ + { + 'name': 'test event 1', + }, + ], + }; + }); + when(mockPeer.sendRequest('getVMTimeline', argThat(equals({ + 'timeOriginMicros': 1000000, + 'timeExtentMicros': 999999, + })))) + .thenAnswer((Invocation invocation) async { + log.add('download 2'); + return { + 'traceEvents': [ + { + 'name': 'test event 2', + }, + ], + }; + }); + + + final Timeline timeline = await driver.traceAction(() async { + log.add('action'); + }); + + expect(log, const [ + 'clear', + 'getVMTimelineMicros', + 'startTracing', + 'action', + 'getVMTimelineMicros', + 'stopTracing', + 'download 1', + 'download 2', + ]); + expect(timeline.events.map((TimelineEvent event) => event.name), [ + 'test event 1', + 'test event 2', + ]); + }); }); group('traceAction with timeline streams', () { @@ -598,6 +670,12 @@ void main() { bool startTracingCalled = false; bool stopTracingCalled = false; + when(mockPeer.sendRequest('getVMTimelineMicros')) + .thenAnswer((Invocation invocation) async { + log.add('getVMTimelineMicros'); + return {}; + }); + when(mockPeer.sendRequest('setVMTimelineFlags', argThat(equals({'recordedStreams': '[Dart, GC, Compiler]'})))) .thenAnswer((Invocation invocation) async { startTracingCalled = true;