Skip to content

Commit 4453301

Browse files
committed
Merge pull request flutter#2689 from yjbanov/benchmark-harness
[driver] API for getting performance traces
2 parents 2fdcb59 + 26c4177 commit 4453301

File tree

3 files changed

+83
-18
lines changed

3 files changed

+83
-18
lines changed

examples/stocks/test_driver/scroll_perf.dart

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

55
import 'package:flutter_driver/driver_extension.dart';
6-
import 'package:flutter_driver/src/error.dart';
76
import 'package:stocks/main.dart' as app;
87

98
void main() {
10-
flutterDriverLog.listen(print);
119
enableFlutterDriverExtension();
1210
app.main();
1311
}

examples/stocks/test_driver/scroll_perf_test.dart

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,29 @@ void main() {
1919
driver.close();
2020
});
2121

22-
test('tap on the floating action button; verify counter', () async {
23-
// Find the scrollable stock list
24-
ObjectRef stockList = await driver.findByValueKey('stock-list');
25-
expect(stockList, isNotNull);
26-
27-
// Scroll down 5 times
28-
for (int i = 0; i < 5; i++) {
29-
await driver.scroll(stockList, 0.0, -300.0, new Duration(milliseconds: 300));
30-
await new Future<Null>.delayed(new Duration(milliseconds: 500));
31-
}
32-
33-
// Scroll up 5 times
34-
for (int i = 0; i < 5; i++) {
35-
await driver.scroll(stockList, 0.0, 300.0, new Duration(milliseconds: 300));
36-
await new Future<Null>.delayed(new Duration(milliseconds: 500));
37-
}
22+
test('measure', () async {
23+
Map<String, dynamic> profileJson = await driver.traceAction(() async {
24+
// Find the scrollable stock list
25+
ObjectRef stockList = await driver.findByValueKey('stock-list');
26+
expect(stockList, isNotNull);
27+
28+
// Scroll down
29+
for (int i = 0; i < 5; i++) {
30+
await driver.scroll(stockList, 0.0, -300.0, new Duration(milliseconds: 300));
31+
await new Future<Null>.delayed(new Duration(milliseconds: 500));
32+
}
33+
34+
// Scroll up
35+
for (int i = 0; i < 5; i++) {
36+
await driver.scroll(stockList, 0.0, 300.0, new Duration(milliseconds: 300));
37+
await new Future<Null>.delayed(new Duration(milliseconds: 500));
38+
}
39+
});
40+
41+
// Usually the profile is saved to a file and then analyzed using
42+
// chrom://tracing or a script. Both are out of scope for this little
43+
// test, so all we do is check that we received something.
44+
expect(profileJson, isNotNull);
3845
});
3946
});
4047
}

packages/flutter_driver/lib/src/driver.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class FlutterDriver {
2929
FlutterDriver.connectedTo(this._serviceClient, this._appIsolate);
3030

3131
static const String _kFlutterExtensionMethod = 'ext.flutter_driver';
32+
static const String _kStartTracingMethod = 'ext.flutter_startTracing';
33+
static const String _kStopTracingMethod = 'ext.flutter_stopTracing';
34+
static const String _kDownloadTraceDataMethod = 'ext.flutter_downloadTraceData';
3235
static const Duration _kDefaultTimeout = const Duration(seconds: 5);
3336
static const Duration _kDefaultPauseBetweenRetries = const Duration(milliseconds: 160);
3437

@@ -205,6 +208,63 @@ class FlutterDriver {
205208
return result.text;
206209
}
207210

211+
/// Starts recording performance traces.
212+
Future<Null> startTracing() async {
213+
try {
214+
await _appIsolate.invokeExtension(_kStartTracingMethod);
215+
return null;
216+
} catch(error, stackTrace) {
217+
throw new DriverError(
218+
'Failed to start tracing due to remote error',
219+
error,
220+
stackTrace
221+
);
222+
}
223+
}
224+
225+
/// Stops recording performance traces and downloads the trace profile.
226+
// TODO(yjbanov): return structured data rather than raw JSON once we have a
227+
// stable protocol to talk to.
228+
Future<Map<String, dynamic>> stopTracingAndDownloadProfile() async {
229+
Map<String, dynamic> stopResult =
230+
await _appIsolate.invokeExtension(_kStopTracingMethod);
231+
String traceFilePath = stopResult['trace_file_path'];
232+
233+
// Tracing data isn't available immediately as some of it is queued up in
234+
// the event loop.
235+
Stopwatch sw = new Stopwatch()..start();
236+
while(sw.elapsed < const Duration(seconds: 30)) {
237+
Map<String, dynamic> downloadResult =
238+
await _appIsolate.invokeExtension(_kDownloadTraceDataMethod, <String, String>{
239+
'trace_file_path': traceFilePath,
240+
});
241+
242+
if (downloadResult['success'] == false)
243+
throw new DriverError('Failed to download trace file: $traceFilePath');
244+
else if (downloadResult['status'] != 'not ready')
245+
return downloadResult;
246+
247+
// Give the event loop a chance to flush the trace log
248+
await new Future<Null>.delayed(const Duration(milliseconds: 200));
249+
}
250+
throw new DriverError(
251+
'Timed out waiting for tracing profile to become ready for download.'
252+
);
253+
}
254+
255+
/// Runs [action] and outputs a performance trace for it.
256+
///
257+
/// Waits for the `Future` returned by [action] to complete prior to stopping
258+
/// the trace.
259+
///
260+
/// This is merely a convenience wrapper on top of [startTracing] and
261+
/// [stopTracingAndDownloadProfile].
262+
Future<Map<String, dynamic>> traceAction(Future<dynamic> action()) async {
263+
await startTracing();
264+
await action();
265+
return stopTracingAndDownloadProfile();
266+
}
267+
208268
/// Calls the [evaluator] repeatedly until the result of the evaluation
209269
/// satisfies the [matcher].
210270
///

0 commit comments

Comments
 (0)