Unverified Commit aeaeded7 authored by Kaushik Iska's avatar Kaushik Iska Committed by GitHub

Add VMService command to get frame rasterization metrics (#100696)

parent 329ceaef
......@@ -118,6 +118,11 @@ class CommandHelp {
'WidgetsApp.showWidgetInspectorOverride',
);
late final CommandHelpOption j = _makeOption(
'j',
'Dump frame raster stats for the current frame.',
);
late final CommandHelpOption k = _makeOption(
'k',
'Toggle CanvasKit rendering.',
......
......@@ -674,6 +674,41 @@ abstract class ResidentHandlers {
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.
Future<bool> debugDumpLayerTree() async {
if (!supportsServiceProtocol || !isRunningDebug) {
......@@ -1439,6 +1474,7 @@ abstract class ResidentRunner extends ResidentHandlers {
if (isRunningDebug) {
commandHelp.g.print();
}
commandHelp.j.print();
}
}
......@@ -1605,6 +1641,9 @@ class TerminalHandler {
return residentRunner.debugToggleWidgetInspector();
case 'I':
return residentRunner.debugToggleInvertOversizedImages();
case 'j':
case 'J':
return residentRunner.debugFrameJankMetrics();
case 'L':
return residentRunner.debugDumpLayerTree();
case 'o':
......
......@@ -23,6 +23,7 @@ const String kRunInViewMethod = '_flutter.runInView';
const String kListViewsMethod = '_flutter.listViews';
const String kScreenshotSkpMethod = '_flutter.screenshotSkp';
const String kScreenshotMethod = '_flutter.screenshot';
const String kRenderFrameWithRasterStatsMethod = '_flutter.renderFrameWithRasterStats';
/// The error response code from an unrecoverable compilation failure.
const int kIsolateReloadBarred = 1005;
......@@ -538,6 +539,26 @@ class FlutterVmService {
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({
required String isolateId,
}) async {
......
......@@ -1330,6 +1330,7 @@ flutter:
commandHelp.a,
commandHelp.M,
commandHelp.g,
commandHelp.j,
commandHelp.hWithDetails,
commandHelp.c,
commandHelp.q,
......
......@@ -459,6 +459,14 @@ void main() {
method: 'getVMTimeline',
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();
expect(timeline, isNull);
final Map<String, Object> rasterStats =
await fakeVmServiceHost.vmService.renderFrameWithRasterStats(viewId: '1', uiIsolateId: '12');
expect(rasterStats, isNull);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
......@@ -502,6 +514,35 @@ void main() {
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 {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
......
......@@ -612,6 +612,7 @@ void main() {
'a Toggle timeline events for all widget build methods. (debugProfileWidgetBuilds)',
'M Write SkSL shaders to a unique file in the project directory.',
'g Run source code generators.',
'j Dump frame raster stats for the current frame.',
'h Repeat this help message.',
'd Detach (terminate "flutter run" but leave application running).',
'c Clear the screen',
......
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