Commit 26c41773 authored by yjbanov's avatar yjbanov

[driver] API for getting performance traces

parent 7012d522
......@@ -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();
}
......@@ -19,22 +19,29 @@ void main() {
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);
// 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 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));
}
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
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);
});
});
}
......@@ -29,6 +29,9 @@ class FlutterDriver {
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 @@ class FlutterDriver {
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].
///
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment