[flutter_tools] Catch rpc error in render frame with raster stats (#144190)

Fixes https://github.com/flutter/flutter/issues/143010. This is intended to be cherrypicked into the 3.19 and 3.20 releases.

Long-term, we should deprecate this feature: https://github.com/flutter/flutter/issues/144191
This commit is contained in:
Christopher Fujino 2024-02-28 10:54:18 -08:00 committed by GitHub
parent 47b0ef8127
commit f89b4f151e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 109 additions and 50 deletions

View file

@ -714,23 +714,30 @@ abstract class ResidentHandlers {
continue;
}
final List<FlutterView> views = await device!.vmService!.getFlutterViews();
for (final FlutterView view in views) {
final Map<String, Object?>? rasterData =
await device.vmService!.renderFrameWithRasterStats(
viewId: view.id,
uiIsolateId: view.uiIsolate!.id,
);
if (rasterData != null) {
final File tempFile = globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter_jank_metrics',
'json',
);
tempFile.writeAsStringSync(jsonEncode(rasterData), flush: true);
logger.printStatus('Wrote jank metrics to ${tempFile.absolute.path}');
} else {
logger.printWarning('Unable to get jank metrics.');
try {
for (final FlutterView view in views) {
final Map<String, Object?>? rasterData =
await device.vmService!.renderFrameWithRasterStats(
viewId: view.id,
uiIsolateId: view.uiIsolate!.id,
);
if (rasterData != null) {
final File tempFile = globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter_jank_metrics',
'json',
);
tempFile.writeAsStringSync(jsonEncode(rasterData), flush: true);
logger.printStatus('Wrote jank metrics to ${tempFile.absolute.path}');
} else {
logger.printWarning('Unable to get jank metrics.');
}
}
} on vm_service.RPCError catch (err) {
if (err.code != RPCErrorCodes.kServerError || !err.message.contains('Raster status not supported on Impeller backend')) {
rethrow;
}
logger.printWarning('Unable to get jank metrics for Impeller renderer');
}
}
return true;

View file

@ -48,13 +48,13 @@ const FakeVmServiceRequest failingCreateDevFSRequest = FakeVmServiceRequest(
args: <String, Object>{
'fsName': 'test',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
);
const FakeVmServiceRequest failingDeleteDevFSRequest = FakeVmServiceRequest(
method: '_deleteDevFS',
args: <String, dynamic>{'fsName': 'test'},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
);
void main() {

View file

@ -264,14 +264,14 @@ void main() {
),
const FakeVmServiceRequest(
method: kListViewsMethod,
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: 'streamCancel',
args: <String, Object>{
'streamId': 'Isolate',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
], httpAddress: Uri.parse('http://localhost:1234'));
@ -340,14 +340,14 @@ void main() {
),
const FakeVmServiceRequest(
method: kListViewsMethod,
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: 'streamCancel',
args: <String, Object>{
'streamId': 'Isolate',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
], httpAddress: Uri.parse('http://localhost:5678'));

View file

@ -114,6 +114,18 @@ final FakeVmServiceRequest listViews = FakeVmServiceRequest(
},
);
const FakeVmServiceRequest renderFrameRasterStats = FakeVmServiceRequest(
method: kRenderFrameWithRasterStatsMethod,
args: <String, Object>{
'viewId': 'a',
'isolateId': '1',
},
error: FakeRPCError(
code: RPCErrorCodes.kServerError,
error: 'Raster status not supported on Impeller backend',
),
);
const FakeVmServiceRequest setAssetBundlePath = FakeVmServiceRequest(
method: '_flutter.setAssetBundlePath',
args: <String, Object>{

View file

@ -333,6 +333,32 @@ void main() {
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner can handle an RPC exception from debugFrameJankMetrics', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
renderFrameRasterStats,
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
final bool result = await residentRunner.debugFrameJankMetrics();
expect(result, true);
expect((globals.flutterUsage as TestUsage).events, isEmpty);
expect(fakeAnalytics.sentEvents, isEmpty);
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
expect((globals.logger as BufferLogger).warningText, contains('Unable to get jank metrics for Impeller renderer'));
}, overrides: <Type, Generator>{
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner fails its operation if the device initialization is not complete', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,

View file

@ -973,7 +973,7 @@ void main() {
const FakeVmServiceRequest(
method: kHotRestartServiceName,
// Failed response,
errorCode: RPCErrorCodes.kInternalError,
error: FakeRPCError(code: RPCErrorCodes.kInternalError),
),
]);
setupMocks();

View file

@ -1125,7 +1125,7 @@ void main() {
const FakeVmServiceRequest(
method: 'ext.dwds.screenshot',
// Failed response,
errorCode: RPCErrorCodes.kInternalError,
error: FakeRPCError(code: RPCErrorCodes.kInternalError),
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
@ -1165,7 +1165,7 @@ void main() {
'enabled': 'true',
},
// Failed response,
errorCode: RPCErrorCodes.kInternalError,
error: const FakeRPCError(code: RPCErrorCodes.kInternalError),
),
],
logger: logger,

View file

@ -140,7 +140,7 @@ void main() {
...vmServiceSetup,
const FakeVmServiceRequest(
method: 'getVMTimeline',
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: 'setVMTimelineFlags',

View file

@ -301,7 +301,7 @@ void main() {
args: <String, Object>{
'isolateId': '1',
},
errorCode: RPCErrorCodes.kMethodNotFound,
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
),
]
);
@ -320,7 +320,7 @@ void main() {
args: <String, Object>{
'isolateId': '1',
},
errorCode: RPCErrorCodes.kMethodNotFound,
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
),
]
);
@ -339,7 +339,7 @@ void main() {
args: <String, Object>{
'isolateId': '1',
},
errorCode: RPCErrorCodes.kMethodNotFound,
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
),
]
);
@ -358,7 +358,7 @@ void main() {
args: <String, Object>{
'isolateId': '1',
},
errorCode: RPCErrorCodes.kMethodNotFound,
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
),
]
);
@ -377,7 +377,7 @@ void main() {
args: <String, Object>{
'isolateId': '1',
},
errorCode: RPCErrorCodes.kMethodNotFound,
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
),
]
);
@ -396,7 +396,7 @@ void main() {
args: <String, Object>{
'isolateId': '1',
},
errorCode: RPCErrorCodes.kMethodNotFound,
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
),
]
);
@ -436,26 +436,26 @@ void main() {
args: <String, Object>{
'viewId': '1234',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: kListViewsMethod,
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: kScreenshotSkpMethod,
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: 'setVMTimelineFlags',
args: <String, dynamic>{
'recordedStreams': <String>['test'],
},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: 'getVMTimeline',
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: kRenderFrameWithRasterStatsMethod,
@ -463,7 +463,7 @@ void main() {
'viewId': '1',
'isolateId': '12',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
]
);
@ -495,9 +495,13 @@ void main() {
testWithoutContext('getIsolateOrNull returns null if service disappears ', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(method: 'getIsolate', args: <String, Object>{
'isolateId': 'isolate/123',
}, errorCode: RPCErrorCodes.kServiceDisappeared),
const FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': 'isolate/123',
},
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
]
);
@ -702,7 +706,7 @@ void main() {
args: <String, Object>{
'isolateId': '1',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
// Assume a different isolate returns.
FakeVmServiceStreamResponse(
@ -734,7 +738,7 @@ void main() {
'streamId': 'Isolate',
},
// Stream already subscribed - https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#streamlisten
errorCode: 103,
error: FakeRPCError(code: 103),
),
listViewsRequest,
FakeVmServiceRequest(
@ -802,14 +806,14 @@ void main() {
),
const FakeVmServiceRequest(
method: kListViewsMethod,
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
const FakeVmServiceRequest(
method: 'streamCancel',
args: <String, Object>{
'streamId': 'Isolate',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
),
]);

View file

@ -39,7 +39,7 @@ class FakeVmServiceHost {
expect(_requests, isEmpty);
return;
}
if (fakeRequest.errorCode == null) {
if (fakeRequest.error == null) {
_input.add(json.encode(<String, Object?>{
'jsonrpc': '2.0',
'id': request['id'],
@ -50,8 +50,8 @@ class FakeVmServiceHost {
'jsonrpc': '2.0',
'id': request['id'],
'error': <String, Object?>{
'code': fakeRequest.errorCode,
'message': 'error',
'code': fakeRequest.error!.code,
'message': fakeRequest.error!.error,
},
}));
}
@ -90,12 +90,22 @@ abstract class VmServiceExpectation {
bool get isRequest;
}
class FakeRPCError {
const FakeRPCError({
required this.code,
this.error = 'error',
});
final int code;
final String error;
}
class FakeVmServiceRequest implements VmServiceExpectation {
const FakeVmServiceRequest({
required this.method,
this.args = const <String, Object?>{},
this.jsonResponse,
this.errorCode,
this.error,
this.close = false,
});
@ -106,7 +116,7 @@ class FakeVmServiceRequest implements VmServiceExpectation {
/// If non-null, the error code for a [vm_service.RPCError] in place of a
/// standard response.
final int? errorCode;
final FakeRPCError? error;
final Map<String, Object?>? args;
final Map<String, Object?>? jsonResponse;