improve web benchmark error reporting (#51490)
diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_build_material_checkbox.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_build_material_checkbox.dart
index 94b018b..8ced6c4 100644
--- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_build_material_checkbox.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_build_material_checkbox.dart
@@ -18,10 +18,15 @@
@override
Widget createWidget() {
- return Column(
- children: List<Widget>.generate(10, (int i) {
- return _buildRow();
- }),
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: Material(
+ child: Column(
+ children: List<Widget>.generate(10, (int i) {
+ return _buildRow();
+ }),
+ ),
+ ),
);
}
diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart
index d47e4cb..d93d89a 100644
--- a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart
@@ -219,6 +219,11 @@
}
@override
+ void _onError(dynamic error, StackTrace stackTrace) {
+ _profileCompleter.completeError(error, stackTrace);
+ }
+
+ @override
Future<Profile> run() {
final _RecordingWidgetsBinding binding =
_RecordingWidgetsBinding.ensureInitialized();
@@ -290,6 +295,11 @@
}
@override
+ void _onError(dynamic error, StackTrace stackTrace) {
+ _profileCompleter.completeError(error, stackTrace);
+ }
+
+ @override
Future<Profile> run() {
final _RecordingWidgetsBinding binding =
_RecordingWidgetsBinding.ensureInitialized();
@@ -512,9 +522,19 @@
/// Implemented by recorders that use [_RecordingWidgetsBinding] to receive
/// frame life-cycle calls.
abstract class _RecordingWidgetsBindingListener {
+ /// Whether the binding should continue pumping frames.
bool _shouldContinue();
+
+ /// Called just before calling [SchedulerBinding.handleDrawFrame].
void _frameWillDraw();
+
+ /// Called immediately after calling [SchedulerBinding.handleDrawFrame].
void _frameDidDraw();
+
+ /// Reports an error.
+ ///
+ /// The implementation is expected to halt benchmark execution as soon as possible.
+ void _onError(dynamic error, StackTrace stackTrace);
}
/// A variant of [WidgetsBinding] that collaborates with a [Recorder] to decide
@@ -543,8 +563,20 @@
}
_RecordingWidgetsBindingListener _listener;
+ bool _hasErrored = false;
void _beginRecording(_RecordingWidgetsBindingListener recorder, Widget widget) {
+ final FlutterExceptionHandler originalOnError = FlutterError.onError;
+
+ // Fail hard and fast on errors. Benchmarks should not have any errors.
+ FlutterError.onError = (FlutterErrorDetails details) {
+ if (_hasErrored) {
+ return;
+ }
+ _listener._onError(details.exception, details.stack);
+ _hasErrored = true;
+ originalOnError(details);
+ };
_listener = recorder;
runApp(widget);
}
@@ -555,19 +587,28 @@
@override
void handleBeginFrame(Duration rawTimeStamp) {
+ // Don't keep on truckin' if there's an error.
+ if (_hasErrored) {
+ return;
+ }
_benchmarkStopped = !_listener._shouldContinue();
super.handleBeginFrame(rawTimeStamp);
}
@override
void scheduleFrame() {
- if (!_benchmarkStopped) {
+ // Don't keep on truckin' if there's an error.
+ if (!_benchmarkStopped && !_hasErrored) {
super.scheduleFrame();
}
}
@override
void handleDrawFrame() {
+ // Don't keep on truckin' if there's an error.
+ if (_hasErrored) {
+ return;
+ }
_listener._frameWillDraw();
super.handleDrawFrame();
_listener._frameDidDraw();
diff --git a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
index d2a050d..b6c38c4 100644
--- a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
@@ -63,23 +63,38 @@
}
final Recorder recorder = recorderFactory();
- final Profile profile = await recorder.run();
- if (!isInManualMode) {
- final html.HttpRequest request = await html.HttpRequest.request(
- '/profile-data',
+ try {
+ final Profile profile = await recorder.run();
+ if (!isInManualMode) {
+ final html.HttpRequest request = await html.HttpRequest.request(
+ '/profile-data',
+ method: 'POST',
+ mimeType: 'application/json',
+ sendData: json.encode(profile.toJson()),
+ );
+ if (request.status != 200) {
+ throw Exception(
+ 'Failed to report profile data to benchmark server. '
+ 'The server responded with status code ${request.status}.'
+ );
+ }
+ } else {
+ print(profile);
+ }
+ } catch (error, stackTrace) {
+ if (isInManualMode) {
+ rethrow;
+ }
+ await html.HttpRequest.request(
+ '/on-error',
method: 'POST',
mimeType: 'application/json',
- sendData: json.encode(profile.toJson()),
+ sendData: json.encode(<String, dynamic>{
+ 'error': '$error',
+ 'stackTrace': '$stackTrace',
+ }),
);
- if (request.status != 200) {
- throw Exception(
- 'Failed to report profile data to benchmark server. '
- 'The server responded with status code ${request.status}.'
- );
- }
- } else {
- print(profile);
}
}
diff --git a/dev/devicelab/lib/tasks/web_benchmarks.dart b/dev/devicelab/lib/tasks/web_benchmarks.dart
index 4c8cb43..ee1da2d 100644
--- a/dev/devicelab/lib/tasks/web_benchmarks.dart
+++ b/dev/devicelab/lib/tasks/web_benchmarks.dart
@@ -53,6 +53,12 @@
}
collectedProfiles.add(profile);
return Response.ok('Profile received');
+ } else if (request.requestedUri.path.endsWith('/on-error')) {
+ final Map<String, dynamic> errorDetails = json.decode(await request.readAsString()) as Map<String, dynamic>;
+ server.close();
+ // Keep the stack trace as a string. It's thrown in the browser, not this Dart VM.
+ profileData.completeError('${errorDetails['error']}\n${errorDetails['stackTrace']}');
+ return Response.ok('');
} else if (request.requestedUri.path.endsWith('/next-benchmark')) {
if (benchmarks == null) {
benchmarks = (json.decode(await request.readAsString()) as List<dynamic>).cast<String>();