[driver] API for getting performance traces
diff --git a/examples/stocks/test_driver/scroll_perf.dart b/examples/stocks/test_driver/scroll_perf.dart index 623f7b7..8188a55 100644 --- a/examples/stocks/test_driver/scroll_perf.dart +++ b/examples/stocks/test_driver/scroll_perf.dart
@@ -3,11 +3,9 @@ // found in the LICENSE file. import 'package:flutter_driver/driver_extension.dart'; -import 'package:flutter_driver/src/error.dart'; import 'package:stocks/main.dart' as app; void main() { - flutterDriverLog.listen(print); enableFlutterDriverExtension(); app.main(); }
diff --git a/examples/stocks/test_driver/scroll_perf_test.dart b/examples/stocks/test_driver/scroll_perf_test.dart index 6165417..a6b3a30 100644 --- a/examples/stocks/test_driver/scroll_perf_test.dart +++ b/examples/stocks/test_driver/scroll_perf_test.dart
@@ -19,22 +19,29 @@ driver.close(); }); - test('tap on the floating action button; verify counter', () async { - // Find the scrollable stock list - ObjectRef stockList = await driver.findByValueKey('stock-list'); - expect(stockList, isNotNull); + test('measure', () async { + Map<String, dynamic> profileJson = await driver.traceAction(() async { + // Find the scrollable stock list + ObjectRef stockList = await driver.findByValueKey('stock-list'); + expect(stockList, isNotNull); - // Scroll down 5 times - for (int i = 0; i < 5; i++) { - await driver.scroll(stockList, 0.0, -300.0, new Duration(milliseconds: 300)); - await new Future<Null>.delayed(new Duration(milliseconds: 500)); - } + // Scroll down + for (int i = 0; i < 5; i++) { + await driver.scroll(stockList, 0.0, -300.0, new Duration(milliseconds: 300)); + await new Future<Null>.delayed(new Duration(milliseconds: 500)); + } - // Scroll up 5 times - for (int i = 0; i < 5; i++) { - await driver.scroll(stockList, 0.0, 300.0, new Duration(milliseconds: 300)); - await new Future<Null>.delayed(new Duration(milliseconds: 500)); - } + // Scroll up + for (int i = 0; i < 5; i++) { + await driver.scroll(stockList, 0.0, 300.0, new Duration(milliseconds: 300)); + await new Future<Null>.delayed(new Duration(milliseconds: 500)); + } + }); + + // Usually the profile is saved to a file and then analyzed using + // chrom://tracing or a script. Both are out of scope for this little + // test, so all we do is check that we received something. + expect(profileJson, isNotNull); }); }); }
diff --git a/packages/flutter_driver/lib/src/driver.dart b/packages/flutter_driver/lib/src/driver.dart index b58aeba..59fd3b3 100644 --- a/packages/flutter_driver/lib/src/driver.dart +++ b/packages/flutter_driver/lib/src/driver.dart
@@ -29,6 +29,9 @@ FlutterDriver.connectedTo(this._serviceClient, this._appIsolate); static const String _kFlutterExtensionMethod = 'ext.flutter_driver'; + static const String _kStartTracingMethod = 'ext.flutter_startTracing'; + static const String _kStopTracingMethod = 'ext.flutter_stopTracing'; + static const String _kDownloadTraceDataMethod = 'ext.flutter_downloadTraceData'; static const Duration _kDefaultTimeout = const Duration(seconds: 5); static const Duration _kDefaultPauseBetweenRetries = const Duration(milliseconds: 160); @@ -205,6 +208,63 @@ return result.text; } + /// Starts recording performance traces. + Future<Null> startTracing() async { + try { + await _appIsolate.invokeExtension(_kStartTracingMethod); + return null; + } catch(error, stackTrace) { + throw new DriverError( + 'Failed to start tracing due to remote error', + error, + stackTrace + ); + } + } + + /// Stops recording performance traces and downloads the trace profile. + // TODO(yjbanov): return structured data rather than raw JSON once we have a + // stable protocol to talk to. + Future<Map<String, dynamic>> stopTracingAndDownloadProfile() async { + Map<String, dynamic> stopResult = + await _appIsolate.invokeExtension(_kStopTracingMethod); + String traceFilePath = stopResult['trace_file_path']; + + // Tracing data isn't available immediately as some of it is queued up in + // the event loop. + Stopwatch sw = new Stopwatch()..start(); + while(sw.elapsed < const Duration(seconds: 30)) { + Map<String, dynamic> downloadResult = + await _appIsolate.invokeExtension(_kDownloadTraceDataMethod, <String, String>{ + 'trace_file_path': traceFilePath, + }); + + if (downloadResult['success'] == false) + throw new DriverError('Failed to download trace file: $traceFilePath'); + else if (downloadResult['status'] != 'not ready') + return downloadResult; + + // Give the event loop a chance to flush the trace log + await new Future<Null>.delayed(const Duration(milliseconds: 200)); + } + throw new DriverError( + 'Timed out waiting for tracing profile to become ready for download.' + ); + } + + /// Runs [action] and outputs a performance trace for it. + /// + /// Waits for the `Future` returned by [action] to complete prior to stopping + /// the trace. + /// + /// This is merely a convenience wrapper on top of [startTracing] and + /// [stopTracingAndDownloadProfile]. + Future<Map<String, dynamic>> traceAction(Future<dynamic> action()) async { + await startTracing(); + await action(); + return stopTracingAndDownloadProfile(); + } + /// Calls the [evaluator] repeatedly until the result of the evaluation /// satisfies the [matcher]. ///