Unverified Commit cd0fbd32 authored by Yegor's avatar Yegor Committed by GitHub

improve web benchmark error reporting (#51490)

parent 213027dd
...@@ -18,10 +18,15 @@ class BenchBuildMaterialCheckbox extends WidgetBuildRecorder { ...@@ -18,10 +18,15 @@ class BenchBuildMaterialCheckbox extends WidgetBuildRecorder {
@override @override
Widget createWidget() { Widget createWidget() {
return Column( return Directionality(
children: List<Widget>.generate(10, (int i) { textDirection: TextDirection.ltr,
return _buildRow(); child: Material(
}), child: Column(
children: List<Widget>.generate(10, (int i) {
return _buildRow();
}),
),
),
); );
} }
......
...@@ -218,6 +218,11 @@ abstract class WidgetRecorder extends Recorder implements _RecordingWidgetsBindi ...@@ -218,6 +218,11 @@ abstract class WidgetRecorder extends Recorder implements _RecordingWidgetsBindi
} }
} }
@override
void _onError(dynamic error, StackTrace stackTrace) {
_profileCompleter.completeError(error, stackTrace);
}
@override @override
Future<Profile> run() { Future<Profile> run() {
final _RecordingWidgetsBinding binding = final _RecordingWidgetsBinding binding =
...@@ -289,6 +294,11 @@ abstract class WidgetBuildRecorder extends Recorder implements _RecordingWidgets ...@@ -289,6 +294,11 @@ abstract class WidgetBuildRecorder extends Recorder implements _RecordingWidgets
} }
} }
@override
void _onError(dynamic error, StackTrace stackTrace) {
_profileCompleter.completeError(error, stackTrace);
}
@override @override
Future<Profile> run() { Future<Profile> run() {
final _RecordingWidgetsBinding binding = final _RecordingWidgetsBinding binding =
...@@ -512,9 +522,19 @@ double _computeStandardDeviationForPopulation(Iterable<double> population) { ...@@ -512,9 +522,19 @@ double _computeStandardDeviationForPopulation(Iterable<double> population) {
/// Implemented by recorders that use [_RecordingWidgetsBinding] to receive /// Implemented by recorders that use [_RecordingWidgetsBinding] to receive
/// frame life-cycle calls. /// frame life-cycle calls.
abstract class _RecordingWidgetsBindingListener { abstract class _RecordingWidgetsBindingListener {
/// Whether the binding should continue pumping frames.
bool _shouldContinue(); bool _shouldContinue();
/// Called just before calling [SchedulerBinding.handleDrawFrame].
void _frameWillDraw(); void _frameWillDraw();
/// Called immediately after calling [SchedulerBinding.handleDrawFrame].
void _frameDidDraw(); 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 /// A variant of [WidgetsBinding] that collaborates with a [Recorder] to decide
...@@ -543,8 +563,20 @@ class _RecordingWidgetsBinding extends BindingBase ...@@ -543,8 +563,20 @@ class _RecordingWidgetsBinding extends BindingBase
} }
_RecordingWidgetsBindingListener _listener; _RecordingWidgetsBindingListener _listener;
bool _hasErrored = false;
void _beginRecording(_RecordingWidgetsBindingListener recorder, Widget widget) { 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; _listener = recorder;
runApp(widget); runApp(widget);
} }
...@@ -555,19 +587,28 @@ class _RecordingWidgetsBinding extends BindingBase ...@@ -555,19 +587,28 @@ class _RecordingWidgetsBinding extends BindingBase
@override @override
void handleBeginFrame(Duration rawTimeStamp) { void handleBeginFrame(Duration rawTimeStamp) {
// Don't keep on truckin' if there's an error.
if (_hasErrored) {
return;
}
_benchmarkStopped = !_listener._shouldContinue(); _benchmarkStopped = !_listener._shouldContinue();
super.handleBeginFrame(rawTimeStamp); super.handleBeginFrame(rawTimeStamp);
} }
@override @override
void scheduleFrame() { void scheduleFrame() {
if (!_benchmarkStopped) { // Don't keep on truckin' if there's an error.
if (!_benchmarkStopped && !_hasErrored) {
super.scheduleFrame(); super.scheduleFrame();
} }
} }
@override @override
void handleDrawFrame() { void handleDrawFrame() {
// Don't keep on truckin' if there's an error.
if (_hasErrored) {
return;
}
_listener._frameWillDraw(); _listener._frameWillDraw();
super.handleDrawFrame(); super.handleDrawFrame();
_listener._frameDidDraw(); _listener._frameDidDraw();
......
...@@ -63,23 +63,38 @@ Future<void> _runBenchmark(String benchmarkName) async { ...@@ -63,23 +63,38 @@ Future<void> _runBenchmark(String benchmarkName) async {
} }
final Recorder recorder = recorderFactory(); final Recorder recorder = recorderFactory();
final Profile profile = await recorder.run();
if (!isInManualMode) { try {
final html.HttpRequest request = await html.HttpRequest.request( final Profile profile = await recorder.run();
'/profile-data', 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', method: 'POST',
mimeType: 'application/json', 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);
} }
} }
......
...@@ -53,6 +53,12 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async { ...@@ -53,6 +53,12 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
} }
collectedProfiles.add(profile); collectedProfiles.add(profile);
return Response.ok('Profile received'); 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')) { } else if (request.requestedUri.path.endsWith('/next-benchmark')) {
if (benchmarks == null) { if (benchmarks == null) {
benchmarks = (json.decode(await request.readAsString()) as List<dynamic>).cast<String>(); benchmarks = (json.decode(await request.readAsString()) as List<dynamic>).cast<String>();
......
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