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';
import 'src/commands/run.dart';
import 'src/commands/run_mojo.dart';
import 'src/commands/screenshot.dart';
import 'src/commands/skia.dart';
import 'src/commands/stop.dart';
import 'src/commands/test.dart';
import 'src/commands/trace.dart';
......@@ -66,6 +67,7 @@ Future<Null> main(List<String> args) async {
..addCommand(new RunCommand())
..addCommand(new RunMojoCommand(hidden: !verboseHelp))
..addCommand(new ScreenshotCommand())
..addCommand(new SkiaCommand())
..addCommand(new StopCommand())
..addCommand(new TestCommand())
..addCommand(new TraceCommand())
......
......@@ -186,7 +186,7 @@ class AndroidDevice extends Device {
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);
try {
......@@ -194,9 +194,9 @@ class AndroidDevice extends Device {
port = await portForwarder.forward(devicePort,
hostPort: port);
if (portWasZero)
printStatus('Observatory listening on http://127.0.0.1:$port');
printStatus('$service listening on http://127.0.0.1:$port');
} 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 {
String route,
bool clearLogs: false,
bool startPaused: false,
int debugPort: observatoryDefaultPort
int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort
}) async {
printTrace('$this startBundle');
......@@ -220,12 +221,15 @@ class AndroidDevice extends Device {
runCheckedSync(adbCommandForDevice(<String>['push', bundlePath, _deviceBundlePath]));
ServiceProtocolDiscovery serviceProtocolDiscovery =
new ServiceProtocolDiscovery(logReader);
ServiceProtocolDiscovery observatoryDiscovery =
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
// 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>[
'shell', 'am', 'start',
......@@ -255,9 +259,14 @@ class AndroidDevice extends Device {
printTrace('Waiting for observatory port to be available...');
try {
int devicePort = await scrapeServicePort.timeout(new Duration(seconds: 12));
printTrace('service protocol port = $devicePort');
await _forwardObservatoryPort(devicePort, debugPort);
List<int> devicePorts = await scrapeServicePorts.timeout(new Duration(seconds: 12));
int observatoryDevicePort = devicePorts[0];
int diagnosticDevicePort = devicePorts[1];
printTrace('observatory port = $observatoryDevicePort');
await _forwardPort(ServiceProtocolDiscovery.kObservatoryService,
observatoryDevicePort, observatoryPort);
await _forwardPort(ServiceProtocolDiscovery.kDiagnosticService,
diagnosticDevicePort, diagnosticPort);
return true;
} catch (error) {
if (error is TimeoutException)
......@@ -278,7 +287,8 @@ class AndroidDevice extends Device {
bool checked: true,
bool clearLogs: false,
bool startPaused: false,
int debugPort: observatoryDefaultPort,
int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs
}) async {
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
......@@ -300,7 +310,8 @@ class AndroidDevice extends Device {
route: route,
clearLogs: clearLogs,
startPaused: startPaused,
debugPort: debugPort
observatoryPort: observatoryPort,
diagnosticPort: diagnosticPort
)) {
return true;
} else {
......
......@@ -3,3 +3,4 @@
// found in the LICENSE file.
const int observatoryDefaultPort = 8181;
const int diagnosticDefaultPort = 8182;
......@@ -268,7 +268,7 @@ Future<int> startApp(DriveCommand command) async {
checked: command.checked,
clearLogs: true,
startPaused: true,
debugPort: command.debugPort,
observatoryPort: command.debugPort,
platformArgs: <String, dynamic>{
'trace-startup': command.traceStartup,
}
......
......@@ -221,7 +221,7 @@ Future<int> startApp(
checked: checked,
clearLogs: clearLogs,
startPaused: startPaused,
debugPort: debugPort,
observatoryPort: debugPort,
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 {
bool checked: true,
bool clearLogs: false,
bool startPaused: false,
int debugPort: observatoryDefaultPort,
int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs
});
......
......@@ -162,7 +162,8 @@ class IOSDevice extends Device {
bool checked: true,
bool clearLogs: false,
bool startPaused: false,
int debugPort: observatoryDefaultPort,
int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs
}) async {
// TODO(chinmaygarde): Use checked, mainPath, route, clearLogs.
......
......@@ -445,7 +445,8 @@ class IOSSimulator extends Device {
bool checked: true,
bool clearLogs: false,
bool startPaused: false,
int debugPort: observatoryDefaultPort,
int observatoryPort: observatoryDefaultPort,
int diagnosticPort: diagnosticDefaultPort,
Map<String, dynamic> platformArgs
}) async {
printTrace('Building ${app.name} for $id.');
......@@ -457,7 +458,7 @@ class IOSSimulator extends Device {
return false;
ServiceProtocolDiscovery serviceProtocolDiscovery =
new ServiceProtocolDiscovery(logReader);
new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService);
// We take this future here but do not wait for completion until *after* we
// start the application.
......@@ -476,8 +477,8 @@ class IOSSimulator extends Device {
if (startPaused)
args.add("--start-paused");
if (debugPort != observatoryDefaultPort)
args.add("--observatory-port=$debugPort");
if (observatoryPort != observatoryDefaultPort)
args.add("--observatory-port=$observatoryPort");
// Launch the updated application in the simulator.
try {
......
......@@ -8,9 +8,13 @@ import 'device.dart';
/// Discover service protocol ports on devices.
class ServiceProtocolDiscovery {
/// [logReader] A [DeviceLogReader] to look for Observatory messages in.
ServiceProtocolDiscovery(DeviceLogReader logReader)
: _logReader = logReader {
static const String kObservatoryService = 'Observatory';
static const String kDiagnosticService = 'Diagnostic server';
/// [logReader] A [DeviceLogReader] to look for service messages in.
ServiceProtocolDiscovery(DeviceLogReader logReader, String serviceName)
: _logReader = logReader,
_serviceName = serviceName {
assert(_logReader != null);
if (!_logReader.isReading)
_logReader.start();
......@@ -19,6 +23,7 @@ class ServiceProtocolDiscovery {
}
final DeviceLogReader _logReader;
final String _serviceName;
Completer<int> _completer = new Completer<int>();
/// The [Future] returned by this function will complete when the next service
......@@ -27,7 +32,7 @@ class ServiceProtocolDiscovery {
void _onLine(String line) {
int portNumber = 0;
if (line.contains('Observatory listening on http://')) {
if (line.contains('$_serviceName listening on http://')) {
try {
RegExp portExp = new RegExp(r"\d+.\d+.\d+.\d+:(\d+)");
String port = portExp.firstMatch(line).group(1);
......
......@@ -13,6 +13,7 @@ dependencies:
args: ^0.13.4
crypto: 0.9.2
file: ^0.1.0
http: ^0.11.3
json_schema: ^1.0.3
mustache4dart: ^1.0.0
package_config: ^0.1.3
......
......@@ -14,7 +14,7 @@ void main() {
test('Discovery Heartbeat', () async {
MockDeviceLogReader logReader = new MockDeviceLogReader();
ServiceProtocolDiscovery discoverer =
new ServiceProtocolDiscovery(logReader);
new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService);
// Get next port future.
Future<int> nextPort = discoverer.nextPort();
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