Add VMService command to get frame rasterization metrics (#100696)
This commit is contained in:
parent
329ceaef66
commit
aeaeded715
@ -118,6 +118,11 @@ class CommandHelp {
|
|||||||
'WidgetsApp.showWidgetInspectorOverride',
|
'WidgetsApp.showWidgetInspectorOverride',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
late final CommandHelpOption j = _makeOption(
|
||||||
|
'j',
|
||||||
|
'Dump frame raster stats for the current frame.',
|
||||||
|
);
|
||||||
|
|
||||||
late final CommandHelpOption k = _makeOption(
|
late final CommandHelpOption k = _makeOption(
|
||||||
'k',
|
'k',
|
||||||
'Toggle CanvasKit rendering.',
|
'Toggle CanvasKit rendering.',
|
||||||
|
@ -674,6 +674,41 @@ abstract class ResidentHandlers {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dump frame rasterization metrics for the last rendered frame.
|
||||||
|
///
|
||||||
|
/// The last frames gets re-painted while recording additional tracing info
|
||||||
|
/// pertaining to the various draw calls issued by the frame. The timings
|
||||||
|
/// recorded here are not indicative of production performance. The intended
|
||||||
|
/// use case is to look at the various layers in proportion to see what
|
||||||
|
/// contributes the most towards raster performance.
|
||||||
|
Future<bool> debugFrameJankMetrics() async {
|
||||||
|
if (!supportsServiceProtocol || !isRunningDebug) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (final FlutterDevice device in flutterDevices) {
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Dump the application's current layer tree to the terminal.
|
/// Dump the application's current layer tree to the terminal.
|
||||||
Future<bool> debugDumpLayerTree() async {
|
Future<bool> debugDumpLayerTree() async {
|
||||||
if (!supportsServiceProtocol || !isRunningDebug) {
|
if (!supportsServiceProtocol || !isRunningDebug) {
|
||||||
@ -1439,6 +1474,7 @@ abstract class ResidentRunner extends ResidentHandlers {
|
|||||||
if (isRunningDebug) {
|
if (isRunningDebug) {
|
||||||
commandHelp.g.print();
|
commandHelp.g.print();
|
||||||
}
|
}
|
||||||
|
commandHelp.j.print();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1605,6 +1641,9 @@ class TerminalHandler {
|
|||||||
return residentRunner.debugToggleWidgetInspector();
|
return residentRunner.debugToggleWidgetInspector();
|
||||||
case 'I':
|
case 'I':
|
||||||
return residentRunner.debugToggleInvertOversizedImages();
|
return residentRunner.debugToggleInvertOversizedImages();
|
||||||
|
case 'j':
|
||||||
|
case 'J':
|
||||||
|
return residentRunner.debugFrameJankMetrics();
|
||||||
case 'L':
|
case 'L':
|
||||||
return residentRunner.debugDumpLayerTree();
|
return residentRunner.debugDumpLayerTree();
|
||||||
case 'o':
|
case 'o':
|
||||||
|
@ -23,6 +23,7 @@ const String kRunInViewMethod = '_flutter.runInView';
|
|||||||
const String kListViewsMethod = '_flutter.listViews';
|
const String kListViewsMethod = '_flutter.listViews';
|
||||||
const String kScreenshotSkpMethod = '_flutter.screenshotSkp';
|
const String kScreenshotSkpMethod = '_flutter.screenshotSkp';
|
||||||
const String kScreenshotMethod = '_flutter.screenshot';
|
const String kScreenshotMethod = '_flutter.screenshot';
|
||||||
|
const String kRenderFrameWithRasterStatsMethod = '_flutter.renderFrameWithRasterStats';
|
||||||
|
|
||||||
/// The error response code from an unrecoverable compilation failure.
|
/// The error response code from an unrecoverable compilation failure.
|
||||||
const int kIsolateReloadBarred = 1005;
|
const int kIsolateReloadBarred = 1005;
|
||||||
@ -538,6 +539,26 @@ class FlutterVmService {
|
|||||||
await onRunnable;
|
await onRunnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders the last frame with additional raster tracing enabled.
|
||||||
|
///
|
||||||
|
/// When a frame is rendered using this method it will incur additional cost
|
||||||
|
/// for rasterization which is not reflective of how long the frame takes in
|
||||||
|
/// production. This is primarily intended to be used to identify the layers
|
||||||
|
/// that result in the most raster perf degradation.
|
||||||
|
Future<Map<String, Object>?> renderFrameWithRasterStats({
|
||||||
|
required String? viewId,
|
||||||
|
required String? uiIsolateId,
|
||||||
|
}) async {
|
||||||
|
final vm_service.Response? response = await callMethodWrapper(
|
||||||
|
kRenderFrameWithRasterStatsMethod,
|
||||||
|
isolateId: uiIsolateId,
|
||||||
|
args: <String, String?>{
|
||||||
|
'viewId': viewId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response?.json as Map<String, Object>?;
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> flutterDebugDumpApp({
|
Future<String> flutterDebugDumpApp({
|
||||||
required String isolateId,
|
required String isolateId,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -1330,6 +1330,7 @@ flutter:
|
|||||||
commandHelp.a,
|
commandHelp.a,
|
||||||
commandHelp.M,
|
commandHelp.M,
|
||||||
commandHelp.g,
|
commandHelp.g,
|
||||||
|
commandHelp.j,
|
||||||
commandHelp.hWithDetails,
|
commandHelp.hWithDetails,
|
||||||
commandHelp.c,
|
commandHelp.c,
|
||||||
commandHelp.q,
|
commandHelp.q,
|
||||||
|
@ -459,6 +459,14 @@ void main() {
|
|||||||
method: 'getVMTimeline',
|
method: 'getVMTimeline',
|
||||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||||
),
|
),
|
||||||
|
const FakeVmServiceRequest(
|
||||||
|
method: kRenderFrameWithRasterStatsMethod,
|
||||||
|
args: <String, dynamic>{
|
||||||
|
'viewId': '1',
|
||||||
|
'isolateId': '12',
|
||||||
|
},
|
||||||
|
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -482,6 +490,10 @@ void main() {
|
|||||||
final vm_service.Response timeline = await fakeVmServiceHost.vmService.getTimeline();
|
final vm_service.Response timeline = await fakeVmServiceHost.vmService.getTimeline();
|
||||||
expect(timeline, isNull);
|
expect(timeline, isNull);
|
||||||
|
|
||||||
|
final Map<String, Object> rasterStats =
|
||||||
|
await fakeVmServiceHost.vmService.renderFrameWithRasterStats(viewId: '1', uiIsolateId: '12');
|
||||||
|
expect(rasterStats, isNull);
|
||||||
|
|
||||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -502,6 +514,35 @@ void main() {
|
|||||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWithoutContext('renderWithStats forwards stats correctly', () async {
|
||||||
|
// ignore: always_specify_types
|
||||||
|
const Map<String, dynamic> response = {
|
||||||
|
'type': 'RenderFrameWithRasterStats',
|
||||||
|
'snapshots':<dynamic>[
|
||||||
|
// ignore: always_specify_types
|
||||||
|
{
|
||||||
|
'layer_unique_id':1512,
|
||||||
|
'duration_micros':477,
|
||||||
|
'snapshot':''
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||||
|
requests: <VmServiceExpectation>[
|
||||||
|
const FakeVmServiceRequest(method: kRenderFrameWithRasterStatsMethod, args: <String, Object>{
|
||||||
|
'isolateId': 'isolate/123',
|
||||||
|
'viewId': 'view/1',
|
||||||
|
}, jsonResponse: response),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
final Map<String, Object> rasterStats =
|
||||||
|
await fakeVmServiceHost.vmService.renderFrameWithRasterStats(viewId: 'view/1', uiIsolateId: 'isolate/123');
|
||||||
|
expect(rasterStats, equals(response));
|
||||||
|
|
||||||
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||||
|
});
|
||||||
|
|
||||||
testWithoutContext('getFlutterViews polls until a view is returned', () async {
|
testWithoutContext('getFlutterViews polls until a view is returned', () async {
|
||||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||||
requests: <VmServiceExpectation>[
|
requests: <VmServiceExpectation>[
|
||||||
|
@ -612,6 +612,7 @@ void main() {
|
|||||||
'a Toggle timeline events for all widget build methods. (debugProfileWidgetBuilds)',
|
'a Toggle timeline events for all widget build methods. (debugProfileWidgetBuilds)',
|
||||||
'M Write SkSL shaders to a unique file in the project directory.',
|
'M Write SkSL shaders to a unique file in the project directory.',
|
||||||
'g Run source code generators.',
|
'g Run source code generators.',
|
||||||
|
'j Dump frame raster stats for the current frame.',
|
||||||
'h Repeat this help message.',
|
'h Repeat this help message.',
|
||||||
'd Detach (terminate "flutter run" but leave application running).',
|
'd Detach (terminate "flutter run" but leave application running).',
|
||||||
'c Clear the screen',
|
'c Clear the screen',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user