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 {
@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();
}),
),
),
);
}
......
......@@ -218,6 +218,11 @@ abstract class WidgetRecorder extends Recorder implements _RecordingWidgetsBindi
}
}
@override
void _onError(dynamic error, StackTrace stackTrace) {
_profileCompleter.completeError(error, stackTrace);
}
@override
Future<Profile> run() {
final _RecordingWidgetsBinding binding =
......@@ -289,6 +294,11 @@ abstract class WidgetBuildRecorder extends Recorder implements _RecordingWidgets
}
}
@override
void _onError(dynamic error, StackTrace stackTrace) {
_profileCompleter.completeError(error, stackTrace);
}
@override
Future<Profile> run() {
final _RecordingWidgetsBinding binding =
......@@ -512,9 +522,19 @@ double _computeStandardDeviationForPopulation(Iterable<double> population) {
/// 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 @@ class _RecordingWidgetsBinding extends BindingBase
}
_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 @@ class _RecordingWidgetsBinding extends BindingBase
@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();
......
......@@ -63,23 +63,38 @@ Future<void> _runBenchmark(String benchmarkName) async {
}
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);
}
}
......
......@@ -53,6 +53,12 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
}
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>();
......
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