Skip to content

Commit

Permalink
Add VMService command to get frame rasterization metrics (#100696)
Browse files Browse the repository at this point in the history
  • Loading branch information
iskakaushik committed Apr 14, 2022
1 parent 329ceae commit aeaeded
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 0 deletions.
5 changes: 5 additions & 0 deletions packages/flutter_tools/lib/src/base/command_help.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
39 changes: 39 additions & 0 deletions packages/flutter_tools/lib/src/resident_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -1439,6 +1474,7 @@ abstract class ResidentRunner extends ResidentHandlers {
if (isRunningDebug) {
commandHelp.g.print();
}
commandHelp.j.print();
}
}

Expand Down Expand Up @@ -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':
Expand Down
21 changes: 21 additions & 0 deletions packages/flutter_tools/lib/src/vmservice.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,7 @@ flutter:
commandHelp.a,
commandHelp.M,
commandHelp.g,
commandHelp.j,
commandHelp.hWithDetails,
commandHelp.c,
commandHelp.q,
Expand Down
41 changes: 41 additions & 0 deletions packages/flutter_tools/test/general.shard/vmservice_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
]
);

Expand All @@ -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);
});

Expand All @@ -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>[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit aeaeded

Please sign in to comment.