Commit 9d022ed2 authored by Jason Simmons's avatar Jason Simmons

Add a command that can capture a Skia picture of a Flutter frame and send it...

Add a command that can capture a Skia picture of a Flutter frame and send it to a file or skiaserve debugger (#3165)
parent 34a67329
...@@ -27,6 +27,7 @@ import 'src/commands/refresh.dart'; ...@@ -27,6 +27,7 @@ import 'src/commands/refresh.dart';
import 'src/commands/run.dart'; import 'src/commands/run.dart';
import 'src/commands/run_mojo.dart'; import 'src/commands/run_mojo.dart';
import 'src/commands/screenshot.dart'; import 'src/commands/screenshot.dart';
import 'src/commands/skia.dart';
import 'src/commands/stop.dart'; import 'src/commands/stop.dart';
import 'src/commands/test.dart'; import 'src/commands/test.dart';
import 'src/commands/trace.dart'; import 'src/commands/trace.dart';
...@@ -66,6 +67,7 @@ Future<Null> main(List<String> args) async { ...@@ -66,6 +67,7 @@ Future<Null> main(List<String> args) async {
..addCommand(new RunCommand()) ..addCommand(new RunCommand())
..addCommand(new RunMojoCommand(hidden: !verboseHelp)) ..addCommand(new RunMojoCommand(hidden: !verboseHelp))
..addCommand(new ScreenshotCommand()) ..addCommand(new ScreenshotCommand())
..addCommand(new SkiaCommand())
..addCommand(new StopCommand()) ..addCommand(new StopCommand())
..addCommand(new TestCommand()) ..addCommand(new TestCommand())
..addCommand(new TraceCommand()) ..addCommand(new TraceCommand())
......
...@@ -186,7 +186,7 @@ class AndroidDevice extends Device { ...@@ -186,7 +186,7 @@ class AndroidDevice extends Device {
return true; return true;
} }
Future<Null> _forwardObservatoryPort(int devicePort, int port) async { Future<Null> _forwardPort(String service, int devicePort, int port) async {
bool portWasZero = (port == null) || (port == 0); bool portWasZero = (port == null) || (port == 0);
try { try {
...@@ -194,9 +194,9 @@ class AndroidDevice extends Device { ...@@ -194,9 +194,9 @@ class AndroidDevice extends Device {
port = await portForwarder.forward(devicePort, port = await portForwarder.forward(devicePort,
hostPort: port); hostPort: port);
if (portWasZero) if (portWasZero)
printStatus('Observatory listening on http://127.0.0.1:$port'); printStatus('$service listening on http://127.0.0.1:$port');
} catch (e) { } catch (e) {
printError('Unable to forward Observatory port $port: $e'); printError('Unable to forward port $port: $e');
} }
} }
...@@ -206,7 +206,8 @@ class AndroidDevice extends Device { ...@@ -206,7 +206,8 @@ class AndroidDevice extends Device {
String route, String route,
bool clearLogs: false, bool clearLogs: false,
bool startPaused: false, bool startPaused: false,
int debugPort: observatoryDefaultPort int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort
}) async { }) async {
printTrace('$this startBundle'); printTrace('$this startBundle');
...@@ -220,12 +221,15 @@ class AndroidDevice extends Device { ...@@ -220,12 +221,15 @@ class AndroidDevice extends Device {
runCheckedSync(adbCommandForDevice(<String>['push', bundlePath, _deviceBundlePath])); runCheckedSync(adbCommandForDevice(<String>['push', bundlePath, _deviceBundlePath]));
ServiceProtocolDiscovery serviceProtocolDiscovery = ServiceProtocolDiscovery observatoryDiscovery =
new ServiceProtocolDiscovery(logReader); new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService);
ServiceProtocolDiscovery diagnosticDiscovery =
new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kDiagnosticService);
// We take this future here but do not wait for completion until *after* we // We take this future here but do not wait for completion until *after* we
// start the bundle. // start the bundle.
Future<int> scrapeServicePort = serviceProtocolDiscovery.nextPort(); Future<List<int>> scrapeServicePorts = Future.wait(
<Future<int>>[observatoryDiscovery.nextPort(), diagnosticDiscovery.nextPort()]);
List<String> cmd = adbCommandForDevice(<String>[ List<String> cmd = adbCommandForDevice(<String>[
'shell', 'am', 'start', 'shell', 'am', 'start',
...@@ -255,9 +259,14 @@ class AndroidDevice extends Device { ...@@ -255,9 +259,14 @@ class AndroidDevice extends Device {
printTrace('Waiting for observatory port to be available...'); printTrace('Waiting for observatory port to be available...');
try { try {
int devicePort = await scrapeServicePort.timeout(new Duration(seconds: 12)); List<int> devicePorts = await scrapeServicePorts.timeout(new Duration(seconds: 12));
printTrace('service protocol port = $devicePort'); int observatoryDevicePort = devicePorts[0];
await _forwardObservatoryPort(devicePort, debugPort); int diagnosticDevicePort = devicePorts[1];
printTrace('observatory port = $observatoryDevicePort');
await _forwardPort(ServiceProtocolDiscovery.kObservatoryService,
observatoryDevicePort, observatoryPort);
await _forwardPort(ServiceProtocolDiscovery.kDiagnosticService,
diagnosticDevicePort, diagnosticPort);
return true; return true;
} catch (error) { } catch (error) {
if (error is TimeoutException) if (error is TimeoutException)
...@@ -278,7 +287,8 @@ class AndroidDevice extends Device { ...@@ -278,7 +287,8 @@ class AndroidDevice extends Device {
bool checked: true, bool checked: true,
bool clearLogs: false, bool clearLogs: false,
bool startPaused: false, bool startPaused: false,
int debugPort: observatoryDefaultPort, int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs Map<String, dynamic> platformArgs
}) async { }) async {
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion()) if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
...@@ -300,7 +310,8 @@ class AndroidDevice extends Device { ...@@ -300,7 +310,8 @@ class AndroidDevice extends Device {
route: route, route: route,
clearLogs: clearLogs, clearLogs: clearLogs,
startPaused: startPaused, startPaused: startPaused,
debugPort: debugPort observatoryPort: observatoryPort,
diagnosticPort: diagnosticPort
)) { )) {
return true; return true;
} else { } else {
......
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
// found in the LICENSE file. // found in the LICENSE file.
const int observatoryDefaultPort = 8181; const int observatoryDefaultPort = 8181;
const int diagnosticDefaultPort = 8182;
...@@ -268,7 +268,7 @@ Future<int> startApp(DriveCommand command) async { ...@@ -268,7 +268,7 @@ Future<int> startApp(DriveCommand command) async {
checked: command.checked, checked: command.checked,
clearLogs: true, clearLogs: true,
startPaused: true, startPaused: true,
debugPort: command.debugPort, observatoryPort: command.debugPort,
platformArgs: <String, dynamic>{ platformArgs: <String, dynamic>{
'trace-startup': command.traceStartup, 'trace-startup': command.traceStartup,
} }
......
...@@ -221,7 +221,7 @@ Future<int> startApp( ...@@ -221,7 +221,7 @@ Future<int> startApp(
checked: checked, checked: checked,
clearLogs: clearLogs, clearLogs: clearLogs,
startPaused: startPaused, startPaused: startPaused,
debugPort: debugPort, observatoryPort: debugPort,
platformArgs: platformArgs platformArgs: platformArgs
); );
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import '../base/common.dart';
import '../globals.dart';
import '../runner/flutter_command.dart';
class SkiaCommand extends FlutterCommand {
@override
final String name = 'skia';
@override
final String description = 'Retrieve the last frame rendered by a Flutter app as a Skia picture.';
SkiaCommand() {
argParser.addOption('output-file', help: 'Write the Skia picture file to this path.');
argParser.addOption('skiaserve', help: 'Post the picture to a skiaserve debugger at this URL.');
argParser.addOption('diagnostic-port',
defaultsTo: diagnosticDefaultPort.toString(),
help: 'Local port where the diagnostic server is listening.');
}
@override
Future<int> runInProject() async {
File outputFile;
Uri skiaserveUri;
if (argResults['output-file'] != null) {
outputFile = new File(argResults['output-file']);
} else if (argResults['skiaserve'] != null) {
skiaserveUri = Uri.parse(argResults['skiaserve']);
} else {
printError('Must provide --output-file or --skiaserve');
return 1;
}
Uri skpUri = new Uri(scheme: 'http', host: '127.0.0.1',
port: int.parse(argResults['diagnostic-port']),
path: '/skp');
http.Request skpRequest = new http.Request('GET', skpUri);
http.StreamedResponse skpResponse = await skpRequest.send();
if (skpResponse.statusCode != HttpStatus.OK) {
String error = await skpResponse.stream.toStringStream().join();
printError('Error: $error');
return 1;
}
if (outputFile != null) {
IOSink sink = outputFile.openWrite();
await sink.addStream(skpResponse.stream);
await sink.close();
} else if (skiaserveUri != null) {
Uri postUri = new Uri.http(skiaserveUri.authority, '/new');
http.MultipartRequest postRequest = new http.MultipartRequest('POST', postUri);
postRequest.files.add(new http.MultipartFile(
'file', skpResponse.stream, skpResponse.contentLength));
http.StreamedResponse postResponse = await postRequest.send();
if (postResponse.statusCode != HttpStatus.OK) {
printError('Failed to post Skia picture to skiaserve');
return 1;
}
}
return 0;
}
}
...@@ -185,7 +185,8 @@ abstract class Device { ...@@ -185,7 +185,8 @@ abstract class Device {
bool checked: true, bool checked: true,
bool clearLogs: false, bool clearLogs: false,
bool startPaused: false, bool startPaused: false,
int debugPort: observatoryDefaultPort, int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs Map<String, dynamic> platformArgs
}); });
......
...@@ -162,7 +162,8 @@ class IOSDevice extends Device { ...@@ -162,7 +162,8 @@ class IOSDevice extends Device {
bool checked: true, bool checked: true,
bool clearLogs: false, bool clearLogs: false,
bool startPaused: false, bool startPaused: false,
int debugPort: observatoryDefaultPort, int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs Map<String, dynamic> platformArgs
}) async { }) async {
// TODO(chinmaygarde): Use checked, mainPath, route, clearLogs. // TODO(chinmaygarde): Use checked, mainPath, route, clearLogs.
......
...@@ -445,7 +445,8 @@ class IOSSimulator extends Device { ...@@ -445,7 +445,8 @@ class IOSSimulator extends Device {
bool checked: true, bool checked: true,
bool clearLogs: false, bool clearLogs: false,
bool startPaused: false, bool startPaused: false,
int debugPort: observatoryDefaultPort, int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs Map<String, dynamic> platformArgs
}) async { }) async {
printTrace('Building ${app.name} for $id.'); printTrace('Building ${app.name} for $id.');
...@@ -457,7 +458,7 @@ class IOSSimulator extends Device { ...@@ -457,7 +458,7 @@ class IOSSimulator extends Device {
return false; return false;
ServiceProtocolDiscovery serviceProtocolDiscovery = ServiceProtocolDiscovery serviceProtocolDiscovery =
new ServiceProtocolDiscovery(logReader); new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService);
// We take this future here but do not wait for completion until *after* we // We take this future here but do not wait for completion until *after* we
// start the application. // start the application.
...@@ -476,8 +477,8 @@ class IOSSimulator extends Device { ...@@ -476,8 +477,8 @@ class IOSSimulator extends Device {
if (startPaused) if (startPaused)
args.add("--start-paused"); args.add("--start-paused");
if (debugPort != observatoryDefaultPort) if (observatoryPort != observatoryDefaultPort)
args.add("--observatory-port=$debugPort"); args.add("--observatory-port=$observatoryPort");
// Launch the updated application in the simulator. // Launch the updated application in the simulator.
try { try {
......
...@@ -8,9 +8,13 @@ import 'device.dart'; ...@@ -8,9 +8,13 @@ import 'device.dart';
/// Discover service protocol ports on devices. /// Discover service protocol ports on devices.
class ServiceProtocolDiscovery { class ServiceProtocolDiscovery {
/// [logReader] A [DeviceLogReader] to look for Observatory messages in. static const String kObservatoryService = 'Observatory';
ServiceProtocolDiscovery(DeviceLogReader logReader) static const String kDiagnosticService = 'Diagnostic server';
: _logReader = logReader {
/// [logReader] A [DeviceLogReader] to look for service messages in.
ServiceProtocolDiscovery(DeviceLogReader logReader, String serviceName)
: _logReader = logReader,
_serviceName = serviceName {
assert(_logReader != null); assert(_logReader != null);
if (!_logReader.isReading) if (!_logReader.isReading)
_logReader.start(); _logReader.start();
...@@ -19,6 +23,7 @@ class ServiceProtocolDiscovery { ...@@ -19,6 +23,7 @@ class ServiceProtocolDiscovery {
} }
final DeviceLogReader _logReader; final DeviceLogReader _logReader;
final String _serviceName;
Completer<int> _completer = new Completer<int>(); Completer<int> _completer = new Completer<int>();
/// The [Future] returned by this function will complete when the next service /// The [Future] returned by this function will complete when the next service
...@@ -27,7 +32,7 @@ class ServiceProtocolDiscovery { ...@@ -27,7 +32,7 @@ class ServiceProtocolDiscovery {
void _onLine(String line) { void _onLine(String line) {
int portNumber = 0; int portNumber = 0;
if (line.contains('Observatory listening on http://')) { if (line.contains('$_serviceName listening on http://')) {
try { try {
RegExp portExp = new RegExp(r"\d+.\d+.\d+.\d+:(\d+)"); RegExp portExp = new RegExp(r"\d+.\d+.\d+.\d+:(\d+)");
String port = portExp.firstMatch(line).group(1); String port = portExp.firstMatch(line).group(1);
......
...@@ -13,6 +13,7 @@ dependencies: ...@@ -13,6 +13,7 @@ dependencies:
args: ^0.13.4 args: ^0.13.4
crypto: 0.9.2 crypto: 0.9.2
file: ^0.1.0 file: ^0.1.0
http: ^0.11.3
json_schema: ^1.0.3 json_schema: ^1.0.3
mustache4dart: ^1.0.0 mustache4dart: ^1.0.0
package_config: ^0.1.3 package_config: ^0.1.3
......
...@@ -14,7 +14,7 @@ void main() { ...@@ -14,7 +14,7 @@ void main() {
test('Discovery Heartbeat', () async { test('Discovery Heartbeat', () async {
MockDeviceLogReader logReader = new MockDeviceLogReader(); MockDeviceLogReader logReader = new MockDeviceLogReader();
ServiceProtocolDiscovery discoverer = ServiceProtocolDiscovery discoverer =
new ServiceProtocolDiscovery(logReader); new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService);
// Get next port future. // Get next port future.
Future<int> nextPort = discoverer.nextPort(); Future<int> nextPort = discoverer.nextPort();
expect(nextPort, isNotNull); expect(nextPort, isNotNull);
......
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