mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Consolidate observatory code (#3892)
* rename service_protocol.dart to protocol_discovery.dart * add a wrapper around the obs. protocol * use json-rpc in run * consolidate obs. code; implement flutter run --benchmark * review comments
This commit is contained in:
parent
ac4ad3bc98
commit
40c0d6ea12
|
@ -14,7 +14,7 @@ import '../build_info.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../flx.dart' as flx;
|
import '../flx.dart' as flx;
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
import '../service_protocol.dart';
|
import '../protocol_discovery.dart';
|
||||||
import 'adb.dart';
|
import 'adb.dart';
|
||||||
import 'android.dart';
|
import 'android.dart';
|
||||||
|
|
||||||
|
@ -243,14 +243,12 @@ class AndroidDevice extends Device {
|
||||||
|
|
||||||
runCheckedSync(adbCommandForDevice(<String>['push', bundlePath, _deviceBundlePath]));
|
runCheckedSync(adbCommandForDevice(<String>['push', bundlePath, _deviceBundlePath]));
|
||||||
|
|
||||||
ServiceProtocolDiscovery observatoryDiscovery;
|
ProtocolDiscovery observatoryDiscovery;
|
||||||
ServiceProtocolDiscovery diagnosticDiscovery;
|
ProtocolDiscovery diagnosticDiscovery;
|
||||||
|
|
||||||
if (options.debuggingEnabled) {
|
if (options.debuggingEnabled) {
|
||||||
observatoryDiscovery = new ServiceProtocolDiscovery(
|
observatoryDiscovery = new ProtocolDiscovery(logReader, ProtocolDiscovery.kObservatoryService);
|
||||||
logReader, ServiceProtocolDiscovery.kObservatoryService);
|
diagnosticDiscovery = new ProtocolDiscovery(logReader, ProtocolDiscovery.kDiagnosticService);
|
||||||
diagnosticDiscovery = new ServiceProtocolDiscovery(
|
|
||||||
logReader, ServiceProtocolDiscovery.kDiagnosticService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> cmd = adbCommandForDevice(<String>[
|
List<String> cmd = adbCommandForDevice(<String>[
|
||||||
|
@ -295,13 +293,11 @@ class AndroidDevice extends Device {
|
||||||
int observatoryLocalPort = await options.findBestObservatoryPort();
|
int observatoryLocalPort = await options.findBestObservatoryPort();
|
||||||
// TODO(devoncarew): Remember the forwarding information (so we can later remove the
|
// TODO(devoncarew): Remember the forwarding information (so we can later remove the
|
||||||
// port forwarding).
|
// port forwarding).
|
||||||
await _forwardPort(ServiceProtocolDiscovery.kObservatoryService,
|
await _forwardPort(ProtocolDiscovery.kObservatoryService, observatoryDevicePort, observatoryLocalPort);
|
||||||
observatoryDevicePort, observatoryLocalPort);
|
|
||||||
int diagnosticDevicePort = devicePorts[1];
|
int diagnosticDevicePort = devicePorts[1];
|
||||||
printTrace('diagnostic port = $diagnosticDevicePort');
|
printTrace('diagnostic port = $diagnosticDevicePort');
|
||||||
int diagnosticLocalPort = await options.findBestDiagnosticPort();
|
int diagnosticLocalPort = await options.findBestDiagnosticPort();
|
||||||
await _forwardPort(ServiceProtocolDiscovery.kDiagnosticService,
|
await _forwardPort(ProtocolDiscovery.kDiagnosticService, diagnosticDevicePort, diagnosticLocalPort);
|
||||||
diagnosticDevicePort, diagnosticLocalPort);
|
|
||||||
return new LaunchResult.succeeded(
|
return new LaunchResult.succeeded(
|
||||||
observatoryPort: observatoryLocalPort,
|
observatoryPort: observatoryLocalPort,
|
||||||
diagnosticPort: diagnosticLocalPort
|
diagnosticPort: diagnosticLocalPort
|
||||||
|
|
|
@ -2,11 +2,6 @@
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
const int defaultObservatoryPort = 8100;
|
const int kDefaultObservatoryPort = 8100;
|
||||||
const int defaultDiagnosticPort = 8101;
|
const int kDefaultDiagnosticPort = 8101;
|
||||||
const int defaultDrivePort = 8183;
|
const int kDefaultDrivePort = 8183;
|
||||||
|
|
||||||
// Names of some of the Timeline events we care about
|
|
||||||
const String flutterEngineMainEnterEventName = 'FlutterEngineMainEnter';
|
|
||||||
const String frameworkInitEventName = 'Framework initialization';
|
|
||||||
const String firstUsefulFrameEventName = 'Widgets completed first useful frame';
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
@ -60,6 +61,10 @@ File getUniqueFile(Directory dir, String baseName, String ext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String toPrettyJson(Object jsonable) {
|
||||||
|
return new JsonEncoder.withIndent(' ').convert(jsonable) + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
/// A class to maintain a list of items, fire events when items are added or
|
/// A class to maintain a list of items, fire events when items are added or
|
||||||
/// removed, and calculate a diff of changes when a new list of items is
|
/// removed, and calculate a diff of changes when a new list of items is
|
||||||
/// available.
|
/// available.
|
||||||
|
|
|
@ -12,7 +12,7 @@ import 'base/logger.dart';
|
||||||
import 'base/os.dart';
|
import 'base/os.dart';
|
||||||
import 'globals.dart';
|
import 'globals.dart';
|
||||||
|
|
||||||
/// A warpper around the `bin/cache/` directory.
|
/// A wrapper around the `bin/cache/` directory.
|
||||||
class Cache {
|
class Cache {
|
||||||
/// [rootOverride] is configurable for testing.
|
/// [rootOverride] is configurable for testing.
|
||||||
Cache({ Directory rootOverride }) {
|
Cache({ Directory rootOverride }) {
|
||||||
|
|
|
@ -376,9 +376,8 @@ class AnalyzeCommand extends FlutterCommand {
|
||||||
'time': (stopwatch.elapsedMilliseconds / 1000.0),
|
'time': (stopwatch.elapsedMilliseconds / 1000.0),
|
||||||
'issues': errorCount
|
'issues': errorCount
|
||||||
};
|
};
|
||||||
JsonEncoder encoder = new JsonEncoder.withIndent(' ');
|
new File(benchmarkOut).writeAsStringSync(toPrettyJson(data));
|
||||||
new File(benchmarkOut).writeAsStringSync(encoder.convert(data) + '\n');
|
printStatus('Analysis benchmark written to $benchmarkOut ($data).');
|
||||||
printStatus('Analysis benchmark written to $benchmarkOut.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class DriveCommand extends RunCommandBase {
|
||||||
);
|
);
|
||||||
|
|
||||||
argParser.addOption('debug-port',
|
argParser.addOption('debug-port',
|
||||||
defaultsTo: defaultDrivePort.toString(),
|
defaultsTo: kDefaultDrivePort.toString(),
|
||||||
help: 'Listen to the given port for a debug connection.'
|
help: 'Listen to the given port for a debug connection.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
@ -11,12 +10,15 @@ import 'package:path/path.dart' as path;
|
||||||
import '../application_package.dart';
|
import '../application_package.dart';
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/logger.dart';
|
import '../base/logger.dart';
|
||||||
|
import '../base/utils.dart';
|
||||||
import '../build_info.dart';
|
import '../build_info.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
|
import '../observatory.dart';
|
||||||
import '../runner/flutter_command.dart';
|
import '../runner/flutter_command.dart';
|
||||||
import 'build_apk.dart';
|
import 'build_apk.dart';
|
||||||
import 'install.dart';
|
import 'install.dart';
|
||||||
|
import 'trace.dart';
|
||||||
|
|
||||||
abstract class RunCommandBase extends FlutterCommand {
|
abstract class RunCommandBase extends FlutterCommand {
|
||||||
RunCommandBase() {
|
RunCommandBase() {
|
||||||
|
@ -62,7 +64,7 @@ class RunCommand extends RunCommandBase {
|
||||||
negatable: false,
|
negatable: false,
|
||||||
help: 'Start in a paused mode and wait for a debugger to connect.');
|
help: 'Start in a paused mode and wait for a debugger to connect.');
|
||||||
argParser.addOption('debug-port',
|
argParser.addOption('debug-port',
|
||||||
help: 'Listen to the given port for a debug connection (defaults to $defaultObservatoryPort).');
|
help: 'Listen to the given port for a debug connection (defaults to $kDefaultObservatoryPort).');
|
||||||
usesPubOption();
|
usesPubOption();
|
||||||
|
|
||||||
// A temporary, hidden flag to experiment with a different run style.
|
// A temporary, hidden flag to experiment with a different run style.
|
||||||
|
@ -72,6 +74,12 @@ class RunCommand extends RunCommandBase {
|
||||||
negatable: false,
|
negatable: false,
|
||||||
hide: true,
|
hide: true,
|
||||||
help: 'Stay resident after running the app.');
|
help: 'Stay resident after running the app.');
|
||||||
|
|
||||||
|
// Hidden option to enable a benchmarking mode. This will run the given
|
||||||
|
// application, measure the startup time and the app restart time, write the
|
||||||
|
// results out to 'refresh_benchmark.json', and exit. This flag is intended
|
||||||
|
// for use in generating automated flutter benchmarks.
|
||||||
|
argParser.addFlag('benchmark', negatable: false, hide: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -107,7 +115,7 @@ class RunCommand extends RunCommandBase {
|
||||||
options = new DebuggingOptions.disabled();
|
options = new DebuggingOptions.disabled();
|
||||||
} else {
|
} else {
|
||||||
options = new DebuggingOptions.enabled(
|
options = new DebuggingOptions.enabled(
|
||||||
// TODO(devoncarew): Check this to 'getBuildMode() == BuildMode.debug'.
|
// TODO(devoncarew): Change this to 'getBuildMode() == BuildMode.debug'.
|
||||||
checked: argResults['checked'],
|
checked: argResults['checked'],
|
||||||
startPaused: argResults['start-paused'],
|
startPaused: argResults['start-paused'],
|
||||||
observatoryPort: debugPort
|
observatoryPort: debugPort
|
||||||
|
@ -119,11 +127,10 @@ class RunCommand extends RunCommandBase {
|
||||||
deviceForCommand,
|
deviceForCommand,
|
||||||
target: target,
|
target: target,
|
||||||
debuggingOptions: options,
|
debuggingOptions: options,
|
||||||
traceStartup: traceStartup,
|
|
||||||
buildMode: getBuildMode()
|
buildMode: getBuildMode()
|
||||||
);
|
);
|
||||||
|
|
||||||
return runner.run();
|
return runner.run(traceStartup: traceStartup, benchmark: argResults['benchmark']);
|
||||||
} else {
|
} else {
|
||||||
return startApp(
|
return startApp(
|
||||||
deviceForCommand,
|
deviceForCommand,
|
||||||
|
@ -132,6 +139,7 @@ class RunCommand extends RunCommandBase {
|
||||||
install: true,
|
install: true,
|
||||||
debuggingOptions: options,
|
debuggingOptions: options,
|
||||||
traceStartup: traceStartup,
|
traceStartup: traceStartup,
|
||||||
|
benchmark: argResults['benchmark'],
|
||||||
route: route,
|
route: route,
|
||||||
buildMode: getBuildMode()
|
buildMode: getBuildMode()
|
||||||
);
|
);
|
||||||
|
@ -146,6 +154,7 @@ Future<int> startApp(
|
||||||
bool install: true,
|
bool install: true,
|
||||||
DebuggingOptions debuggingOptions,
|
DebuggingOptions debuggingOptions,
|
||||||
bool traceStartup: false,
|
bool traceStartup: false,
|
||||||
|
bool benchmark: false,
|
||||||
String route,
|
String route,
|
||||||
BuildMode buildMode: BuildMode.debug
|
BuildMode buildMode: BuildMode.debug
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -169,6 +178,8 @@ Future<int> startApp(
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stopwatch stopwatch = new Stopwatch()..start();
|
||||||
|
|
||||||
// TODO(devoncarew): We shouldn't have to do type checks here.
|
// TODO(devoncarew): We shouldn't have to do type checks here.
|
||||||
if (install && device is AndroidDevice) {
|
if (install && device is AndroidDevice) {
|
||||||
printTrace('Running build command.');
|
printTrace('Running build command.');
|
||||||
|
@ -221,10 +232,22 @@ Future<int> startApp(
|
||||||
platformArgs: platformArgs
|
platformArgs: platformArgs
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result.started)
|
stopwatch.stop();
|
||||||
|
|
||||||
|
if (!result.started) {
|
||||||
printError('Error running application on ${device.name}.');
|
printError('Error running application on ${device.name}.');
|
||||||
else if (traceStartup)
|
} else if (traceStartup) {
|
||||||
await _downloadStartupTrace(result.observatoryPort, device);
|
try {
|
||||||
|
Observatory observatory = await Observatory.connect(result.observatoryPort);
|
||||||
|
await _downloadStartupTrace(observatory);
|
||||||
|
} catch (error) {
|
||||||
|
printError('Error connecting to observatory: $error');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (benchmark)
|
||||||
|
_writeBenchmark(stopwatch);
|
||||||
|
|
||||||
return result.started ? 0 : 2;
|
return result.started ? 0 : 2;
|
||||||
}
|
}
|
||||||
|
@ -241,87 +264,6 @@ String findMainDartFile([String target]) {
|
||||||
return targetPath;
|
return targetPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Null> _downloadStartupTrace(int observatoryPort, Device device) async {
|
|
||||||
Map<String, dynamic> timeline = await device.stopTracingAndDownloadTimeline(
|
|
||||||
observatoryPort,
|
|
||||||
waitForFirstFrame: true
|
|
||||||
);
|
|
||||||
|
|
||||||
int extractInstantEventTimestamp(String eventName) {
|
|
||||||
List<Map<String, dynamic>> events = timeline['traceEvents'];
|
|
||||||
Map<String, dynamic> event = events
|
|
||||||
.firstWhere((Map<String, dynamic> event) => event['name'] == eventName, orElse: () => null);
|
|
||||||
if (event == null)
|
|
||||||
return null;
|
|
||||||
return event['ts'];
|
|
||||||
}
|
|
||||||
|
|
||||||
int engineEnterTimestampMicros = extractInstantEventTimestamp(flutterEngineMainEnterEventName);
|
|
||||||
int frameworkInitTimestampMicros = extractInstantEventTimestamp(frameworkInitEventName);
|
|
||||||
int firstFrameTimestampMicros = extractInstantEventTimestamp(firstUsefulFrameEventName);
|
|
||||||
|
|
||||||
if (engineEnterTimestampMicros == null) {
|
|
||||||
printError('Engine start event is missing in the timeline. Cannot compute startup time.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstFrameTimestampMicros == null) {
|
|
||||||
printError('First frame event is missing in the timeline. Cannot compute startup time.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
File traceInfoFile = new File('build/start_up_info.json');
|
|
||||||
int timeToFirstFrameMicros = firstFrameTimestampMicros - engineEnterTimestampMicros;
|
|
||||||
Map<String, dynamic> traceInfo = <String, dynamic>{
|
|
||||||
'engineEnterTimestampMicros': engineEnterTimestampMicros,
|
|
||||||
'timeToFirstFrameMicros': timeToFirstFrameMicros,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (frameworkInitTimestampMicros != null) {
|
|
||||||
traceInfo['timeToFrameworkInitMicros'] = frameworkInitTimestampMicros - engineEnterTimestampMicros;
|
|
||||||
traceInfo['timeAfterFrameworkInitMicros'] = firstFrameTimestampMicros - frameworkInitTimestampMicros;
|
|
||||||
}
|
|
||||||
|
|
||||||
await traceInfoFile.writeAsString(JSON.encode(traceInfo));
|
|
||||||
|
|
||||||
String timeToFirstFrameMessage;
|
|
||||||
if (timeToFirstFrameMicros > 1000000) {
|
|
||||||
timeToFirstFrameMessage = '${(timeToFirstFrameMicros / 1000000).toStringAsFixed(2)} seconds';
|
|
||||||
} else {
|
|
||||||
timeToFirstFrameMessage = '${timeToFirstFrameMicros ~/ 1000} milliseconds';
|
|
||||||
}
|
|
||||||
|
|
||||||
printStatus('Time to first frame $timeToFirstFrameMessage');
|
|
||||||
printStatus('Saved startup trace info in ${traceInfoFile.path}');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delay until the Observatory / service protocol is available.
|
|
||||||
///
|
|
||||||
/// This does not fail if we're unable to connect, and times out after the given
|
|
||||||
/// [timeout].
|
|
||||||
Future<Null> delayUntilObservatoryAvailable(String host, int port, {
|
|
||||||
Duration timeout: const Duration(seconds: 10)
|
|
||||||
}) async {
|
|
||||||
printTrace('Waiting until Observatory is available (port $port).');
|
|
||||||
|
|
||||||
final String url = 'ws://$host:$port/ws';
|
|
||||||
printTrace('Looking for the observatory at $url.');
|
|
||||||
Stopwatch stopwatch = new Stopwatch()..start();
|
|
||||||
|
|
||||||
while (stopwatch.elapsed <= timeout) {
|
|
||||||
try {
|
|
||||||
WebSocket ws = await WebSocket.connect(url);
|
|
||||||
printTrace('Connected to the observatory port.');
|
|
||||||
ws.close().catchError((dynamic error) => null);
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
await new Future<Null>.delayed(new Duration(milliseconds: 250));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printTrace('Unable to connect to the observatory.');
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getMissingPackageHintForPlatform(TargetPlatform platform) {
|
String _getMissingPackageHintForPlatform(TargetPlatform platform) {
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case TargetPlatform.android_arm:
|
case TargetPlatform.android_arm:
|
||||||
|
@ -346,25 +288,22 @@ class _RunAndStayResident {
|
||||||
this.device, {
|
this.device, {
|
||||||
this.target,
|
this.target,
|
||||||
this.debuggingOptions,
|
this.debuggingOptions,
|
||||||
this.traceStartup : false,
|
|
||||||
this.buildMode : BuildMode.debug
|
this.buildMode : BuildMode.debug
|
||||||
});
|
});
|
||||||
|
|
||||||
final Device device;
|
final Device device;
|
||||||
final String target;
|
final String target;
|
||||||
final DebuggingOptions debuggingOptions;
|
final DebuggingOptions debuggingOptions;
|
||||||
final bool traceStartup;
|
|
||||||
final BuildMode buildMode;
|
final BuildMode buildMode;
|
||||||
|
|
||||||
Completer<int> _exitCompleter;
|
Completer<int> _exitCompleter;
|
||||||
StreamSubscription<String> _loggingSubscription;
|
StreamSubscription<String> _loggingSubscription;
|
||||||
|
|
||||||
WebSocket _observatoryConnection;
|
Observatory observatory;
|
||||||
String _isolateId;
|
String _isolateId;
|
||||||
int _messageId = 0;
|
|
||||||
|
|
||||||
/// Start the app and keep the process running during its lifetime.
|
/// Start the app and keep the process running during its lifetime.
|
||||||
Future<int> run() async {
|
Future<int> run({ bool traceStartup: false, bool benchmark: false }) async {
|
||||||
String mainPath = findMainDartFile(target);
|
String mainPath = findMainDartFile(target);
|
||||||
if (!FileSystemEntity.isFileSync(mainPath)) {
|
if (!FileSystemEntity.isFileSync(mainPath)) {
|
||||||
String message = 'Tried to run $mainPath, but that file does not exist.';
|
String message = 'Tried to run $mainPath, but that file does not exist.';
|
||||||
|
@ -385,6 +324,8 @@ class _RunAndStayResident {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stopwatch stopwatch = new Stopwatch()..start();
|
||||||
|
|
||||||
// TODO(devoncarew): We shouldn't have to do type checks here.
|
// TODO(devoncarew): We shouldn't have to do type checks here.
|
||||||
if (device is AndroidDevice) {
|
if (device is AndroidDevice) {
|
||||||
printTrace('Running build command.');
|
printTrace('Running build command.');
|
||||||
|
@ -440,89 +381,79 @@ class _RunAndStayResident {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopwatch.stop();
|
||||||
|
|
||||||
_exitCompleter = new Completer<int>();
|
_exitCompleter = new Completer<int>();
|
||||||
|
|
||||||
// Connect to observatory.
|
// Connect to observatory.
|
||||||
if (debuggingOptions.debuggingEnabled) {
|
if (debuggingOptions.debuggingEnabled) {
|
||||||
final String localhost = InternetAddress.LOOPBACK_IP_V4.address;
|
observatory = await Observatory.connect(result.observatoryPort);
|
||||||
final String url = 'ws://$localhost:${result.observatoryPort}/ws';
|
|
||||||
|
|
||||||
_observatoryConnection = await WebSocket.connect(url);
|
|
||||||
printTrace('Connected to observatory port: ${result.observatoryPort}.');
|
printTrace('Connected to observatory port: ${result.observatoryPort}.');
|
||||||
|
|
||||||
// Listen for observatory connection close.
|
observatory.onIsolateEvent.listen((Event event) {
|
||||||
_observatoryConnection.listen((dynamic data) {
|
if (event['isolate'] != null)
|
||||||
if (data is String) {
|
_isolateId = event['isolate']['id'];
|
||||||
Map<String, dynamic> json = JSON.decode(data);
|
});
|
||||||
|
observatory.streamListen('Isolate');
|
||||||
|
|
||||||
if (json['method'] == 'streamNotify') {
|
// Listen for observatory connection close.
|
||||||
Map<String, dynamic> event = json['params']['event'];
|
observatory.done.whenComplete(() {
|
||||||
if (event['isolate'] != null && _isolateId == null)
|
|
||||||
_isolateId = event['isolate']['id'];
|
|
||||||
} else if (json['result'] != null && json['result']['type'] == 'VM') {
|
|
||||||
// isolates: [{
|
|
||||||
// type: @Isolate, fixedId: true, id: isolates/724543296, name: dev.flx$main, number: 724543296
|
|
||||||
// }]
|
|
||||||
List<dynamic> isolates = json['result']['isolates'];
|
|
||||||
if (isolates.isNotEmpty)
|
|
||||||
_isolateId = isolates.first['id'];
|
|
||||||
} else if (json['error'] != null) {
|
|
||||||
printError('Error: ${json['error']['message']}.');
|
|
||||||
printTrace(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, onDone: () {
|
|
||||||
_handleExit();
|
_handleExit();
|
||||||
});
|
});
|
||||||
|
|
||||||
_observatoryConnection.add(JSON.encode(<String, dynamic>{
|
observatory.getVM().then((VM vm) {
|
||||||
'method': 'streamListen',
|
if (vm.isolates.isNotEmpty)
|
||||||
'params': <String, dynamic>{ 'streamId': 'Isolate' },
|
_isolateId = vm.isolates.first['id'];
|
||||||
'id': _messageId++
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
_observatoryConnection.add(JSON.encode(<String, dynamic>{
|
|
||||||
'method': 'getVM',
|
|
||||||
'id': _messageId++
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printStatus('Application running.');
|
printStatus('Application running.');
|
||||||
_printHelp();
|
|
||||||
|
|
||||||
terminal.singleCharMode = true;
|
if (observatory != null && traceStartup) {
|
||||||
|
printStatus('Downloading startup trace info...');
|
||||||
|
|
||||||
terminal.onCharInput.listen((String code) {
|
await _downloadStartupTrace(observatory);
|
||||||
String lower = code.toLowerCase();
|
|
||||||
|
|
||||||
if (lower == 'h' || code == AnsiTerminal.KEY_F1) {
|
_handleExit();
|
||||||
// F1, help
|
} else {
|
||||||
_printHelp();
|
_printHelp();
|
||||||
} else if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
|
|
||||||
// F5, refresh
|
terminal.singleCharMode = true;
|
||||||
_handleRefresh();
|
|
||||||
} else if (lower == 'q' || code == AnsiTerminal.KEY_F10) {
|
terminal.onCharInput.listen((String code) {
|
||||||
// F10, exit
|
String lower = code.toLowerCase();
|
||||||
|
|
||||||
|
if (lower == 'h' || code == AnsiTerminal.KEY_F1) {
|
||||||
|
// F1, help
|
||||||
|
_printHelp();
|
||||||
|
} else if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
|
||||||
|
// F5, refresh
|
||||||
|
_handleRefresh();
|
||||||
|
} else if (lower == 'q' || code == AnsiTerminal.KEY_F10) {
|
||||||
|
// F10, exit
|
||||||
|
_handleExit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ProcessSignal.SIGINT.watch().listen((ProcessSignal signal) {
|
||||||
_handleExit();
|
_handleExit();
|
||||||
}
|
});
|
||||||
});
|
ProcessSignal.SIGTERM.watch().listen((ProcessSignal signal) {
|
||||||
|
_handleExit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ProcessSignal.SIGINT.watch().listen((ProcessSignal signal) {
|
if (benchmark) {
|
||||||
_handleExit();
|
_writeBenchmark(stopwatch);
|
||||||
});
|
new Future<Null>.delayed(new Duration(seconds: 2)).then((_) {
|
||||||
ProcessSignal.SIGTERM.watch().listen((ProcessSignal signal) {
|
_handleExit();
|
||||||
_handleExit();
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
return _exitCompleter.future.then((int exitCode) async {
|
return _exitCompleter.future.then((int exitCode) async {
|
||||||
if (_observatoryConnection != null &&
|
if (observatory != null && !observatory.isClosed && _isolateId != null) {
|
||||||
_observatoryConnection.readyState == WebSocket.OPEN &&
|
observatory.flutterExit(_isolateId);
|
||||||
_isolateId != null) {
|
|
||||||
_observatoryConnection.add(JSON.encode(<String, dynamic>{
|
|
||||||
'method': 'ext.flutter.exit',
|
|
||||||
'params': <String, dynamic>{ 'isolateId': _isolateId },
|
|
||||||
'id': _messageId++
|
|
||||||
}));
|
|
||||||
// WebSockets do not have a flush() method.
|
// WebSockets do not have a flush() method.
|
||||||
await new Future<Null>.delayed(new Duration(milliseconds: 100));
|
await new Future<Null>.delayed(new Duration(milliseconds: 100));
|
||||||
}
|
}
|
||||||
|
@ -536,27 +467,80 @@ class _RunAndStayResident {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleRefresh() {
|
void _handleRefresh() {
|
||||||
if (_observatoryConnection == null) {
|
if (observatory == null) {
|
||||||
printError('Debugging is not enabled.');
|
printError('Debugging is not enabled.');
|
||||||
} else {
|
} else {
|
||||||
printStatus('Re-starting application...');
|
printStatus('Re-starting application...');
|
||||||
|
|
||||||
// TODO(devoncarew): Show an error if the isolate reload fails.
|
observatory.isolateReload(_isolateId).catchError((dynamic error) {
|
||||||
_observatoryConnection.add(JSON.encode(<String, dynamic>{
|
printError('Error restarting app: $error');
|
||||||
'method': 'isolateReload',
|
});
|
||||||
'params': <String, dynamic>{ 'isolateId': _isolateId },
|
|
||||||
'id': _messageId++
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleExit() {
|
void _handleExit() {
|
||||||
|
terminal.singleCharMode = false;
|
||||||
|
|
||||||
if (!_exitCompleter.isCompleted) {
|
if (!_exitCompleter.isCompleted) {
|
||||||
_loggingSubscription?.cancel();
|
_loggingSubscription?.cancel();
|
||||||
printStatus('');
|
|
||||||
printStatus('Application finished.');
|
printStatus('Application finished.');
|
||||||
terminal.singleCharMode = false;
|
|
||||||
_exitCompleter.complete(0);
|
_exitCompleter.complete(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Null> _downloadStartupTrace(Observatory observatory) async {
|
||||||
|
Tracing tracing = new Tracing(observatory);
|
||||||
|
|
||||||
|
Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline(
|
||||||
|
waitForFirstFrame: true
|
||||||
|
);
|
||||||
|
|
||||||
|
int extractInstantEventTimestamp(String eventName) {
|
||||||
|
List<Map<String, dynamic>> events = timeline['traceEvents'];
|
||||||
|
Map<String, dynamic> event = events.firstWhere(
|
||||||
|
(Map<String, dynamic> event) => event['name'] == eventName, orElse: () => null
|
||||||
|
);
|
||||||
|
return event == null ? null : event['ts'];
|
||||||
|
}
|
||||||
|
|
||||||
|
int engineEnterTimestampMicros = extractInstantEventTimestamp(kFlutterEngineMainEnterEventName);
|
||||||
|
int frameworkInitTimestampMicros = extractInstantEventTimestamp(kFrameworkInitEventName);
|
||||||
|
int firstFrameTimestampMicros = extractInstantEventTimestamp(kFirstUsefulFrameEventName);
|
||||||
|
|
||||||
|
if (engineEnterTimestampMicros == null) {
|
||||||
|
printError('Engine start event is missing in the timeline. Cannot compute startup time.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstFrameTimestampMicros == null) {
|
||||||
|
printError('First frame event is missing in the timeline. Cannot compute startup time.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
File traceInfoFile = new File('build/start_up_info.json');
|
||||||
|
int timeToFirstFrameMicros = firstFrameTimestampMicros - engineEnterTimestampMicros;
|
||||||
|
Map<String, dynamic> traceInfo = <String, dynamic>{
|
||||||
|
'engineEnterTimestampMicros': engineEnterTimestampMicros,
|
||||||
|
'timeToFirstFrameMicros': timeToFirstFrameMicros,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (frameworkInitTimestampMicros != null) {
|
||||||
|
traceInfo['timeToFrameworkInitMicros'] = frameworkInitTimestampMicros - engineEnterTimestampMicros;
|
||||||
|
traceInfo['timeAfterFrameworkInitMicros'] = firstFrameTimestampMicros - frameworkInitTimestampMicros;
|
||||||
|
}
|
||||||
|
|
||||||
|
traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo));
|
||||||
|
|
||||||
|
printStatus('Time to first frame: ${timeToFirstFrameMicros ~/ 1000}ms.');
|
||||||
|
printStatus('Saved startup trace info in ${traceInfoFile.path}.');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _writeBenchmark(Stopwatch stopwatch) {
|
||||||
|
final String benchmarkOut = 'refresh_benchmark.json';
|
||||||
|
Map<String, dynamic> data = <String, dynamic>{
|
||||||
|
'time': stopwatch.elapsedMilliseconds
|
||||||
|
};
|
||||||
|
new File(benchmarkOut).writeAsStringSync(toPrettyJson(data));
|
||||||
|
printStatus('Run benchmark written to $benchmarkOut ($data).');
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class SkiaCommand extends FlutterCommand {
|
||||||
argParser.addOption('output-file', help: 'Write the Skia picture file to this path.');
|
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('skiaserve', help: 'Post the picture to a skiaserve debugger at this URL.');
|
||||||
argParser.addOption('diagnostic-port',
|
argParser.addOption('diagnostic-port',
|
||||||
defaultsTo: defaultDiagnosticPort.toString(),
|
defaultsTo: kDefaultDiagnosticPort.toString(),
|
||||||
help: 'Local port where the diagnostic server is listening.');
|
help: 'Local port where the diagnostic server is listening.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,15 @@ import 'dart:io';
|
||||||
|
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/utils.dart';
|
import '../base/utils.dart';
|
||||||
import '../device.dart';
|
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
|
import '../observatory.dart';
|
||||||
import '../runner/flutter_command.dart';
|
import '../runner/flutter_command.dart';
|
||||||
|
|
||||||
|
// Names of some of the Timeline events we care about.
|
||||||
|
const String kFlutterEngineMainEnterEventName = 'FlutterEngineMainEnter';
|
||||||
|
const String kFrameworkInitEventName = 'Framework initialization';
|
||||||
|
const String kFirstUsefulFrameEventName = 'Widgets completed first useful frame';
|
||||||
|
|
||||||
class TraceCommand extends FlutterCommand {
|
class TraceCommand extends FlutterCommand {
|
||||||
TraceCommand() {
|
TraceCommand() {
|
||||||
argParser.addFlag('start', negatable: false, help: 'Start tracing.');
|
argParser.addFlag('start', negatable: false, help: 'Start tracing.');
|
||||||
|
@ -20,7 +25,7 @@ class TraceCommand extends FlutterCommand {
|
||||||
argParser.addOption('duration',
|
argParser.addOption('duration',
|
||||||
defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.');
|
defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.');
|
||||||
argParser.addOption('debug-port',
|
argParser.addOption('debug-port',
|
||||||
defaultsTo: defaultObservatoryPort.toString(),
|
defaultsTo: kDefaultObservatoryPort.toString(),
|
||||||
help: 'Local port where the observatory is listening.');
|
help: 'Local port where the observatory is listening.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,38 +46,104 @@ class TraceCommand extends FlutterCommand {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> runInProject() async {
|
Future<int> runInProject() async {
|
||||||
Device device = deviceForCommand;
|
|
||||||
int observatoryPort = int.parse(argResults['debug-port']);
|
int observatoryPort = int.parse(argResults['debug-port']);
|
||||||
|
|
||||||
|
Tracing tracing;
|
||||||
|
|
||||||
|
try {
|
||||||
|
tracing = await Tracing.connect(observatoryPort);
|
||||||
|
} catch (error) {
|
||||||
|
printError('Error connecting to observatory: $error');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if ((!argResults['start'] && !argResults['stop']) ||
|
if ((!argResults['start'] && !argResults['stop']) ||
|
||||||
(argResults['start'] && argResults['stop'])) {
|
(argResults['start'] && argResults['stop'])) {
|
||||||
// Setting neither flags or both flags means do both commands and wait
|
// Setting neither flags or both flags means do both commands and wait
|
||||||
// duration seconds in between.
|
// duration seconds in between.
|
||||||
await device.startTracing(observatoryPort);
|
await tracing.startTracing();
|
||||||
await new Future<Null>.delayed(
|
await new Future<Null>.delayed(
|
||||||
new Duration(seconds: int.parse(argResults['duration'])),
|
new Duration(seconds: int.parse(argResults['duration'])),
|
||||||
() => _stopTracing(device, observatoryPort)
|
() => _stopTracing(tracing)
|
||||||
);
|
);
|
||||||
} else if (argResults['stop']) {
|
} else if (argResults['stop']) {
|
||||||
await _stopTracing(device, observatoryPort);
|
await _stopTracing(tracing);
|
||||||
} else {
|
} else {
|
||||||
await device.startTracing(observatoryPort);
|
await tracing.startTracing();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Null> _stopTracing(Device device, int observatoryPort) async {
|
Future<Null> _stopTracing(Tracing tracing) async {
|
||||||
Map<String, dynamic> timeline = await device.stopTracingAndDownloadTimeline(observatoryPort);
|
Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline();
|
||||||
|
|
||||||
String outPath = argResults['out'];
|
|
||||||
File localFile;
|
File localFile;
|
||||||
if (outPath != null) {
|
|
||||||
localFile = new File(outPath);
|
if (argResults['out'] != null) {
|
||||||
|
localFile = new File(argResults['out']);
|
||||||
} else {
|
} else {
|
||||||
localFile = getUniqueFile(Directory.current, 'trace', 'json');
|
localFile = getUniqueFile(Directory.current, 'trace', 'json');
|
||||||
}
|
}
|
||||||
|
|
||||||
await localFile.writeAsString(JSON.encode(timeline));
|
await localFile.writeAsString(JSON.encode(timeline));
|
||||||
|
|
||||||
printStatus('Trace file saved to ${localFile.path}');
|
printStatus('Trace file saved to ${localFile.path}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Tracing {
|
||||||
|
Tracing(this.observatory);
|
||||||
|
|
||||||
|
static Future<Tracing> connect(int port) {
|
||||||
|
return Observatory.connect(port).then((Observatory observatory) => new Tracing(observatory));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Observatory observatory;
|
||||||
|
|
||||||
|
Future<Null> startTracing() async {
|
||||||
|
await observatory.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
|
||||||
|
await observatory.clearVMTimeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops tracing; optionally wait for first frame.
|
||||||
|
Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({
|
||||||
|
bool waitForFirstFrame: false
|
||||||
|
}) async {
|
||||||
|
Response timeline;
|
||||||
|
|
||||||
|
if (!waitForFirstFrame) {
|
||||||
|
// Stop tracing immediately and get the timeline
|
||||||
|
await observatory.setVMTimelineFlags(<String>[]);
|
||||||
|
timeline = await observatory.getVMTimeline();
|
||||||
|
} else {
|
||||||
|
Completer<Null> whenFirstFrameRendered = new Completer<Null>();
|
||||||
|
|
||||||
|
observatory.onTimelineEvent.listen((Event timelineEvent) {
|
||||||
|
List<Map<String, dynamic>> events = timelineEvent['timelineEvents'];
|
||||||
|
for (Map<String, dynamic> event in events) {
|
||||||
|
if (event['name'] == kFirstUsefulFrameEventName)
|
||||||
|
whenFirstFrameRendered.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await observatory.streamListen('Timeline');
|
||||||
|
|
||||||
|
await whenFirstFrameRendered.future.timeout(
|
||||||
|
const Duration(seconds: 10),
|
||||||
|
onTimeout: () {
|
||||||
|
printError(
|
||||||
|
'Timed out waiting for the first frame event. Either the '
|
||||||
|
'application failed to start, or the event was missed because '
|
||||||
|
'"flutter run" took too long to subscribe to timeline events.'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
timeline = await observatory.getVMTimeline();
|
||||||
|
|
||||||
|
await observatory.setVMTimelineFlags(<String>[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeline.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,6 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
|
|
||||||
import 'package:web_socket_channel/io.dart';
|
|
||||||
|
|
||||||
import 'android/android_device.dart';
|
import 'android/android_device.dart';
|
||||||
import 'application_package.dart';
|
import 'application_package.dart';
|
||||||
import 'base/common.dart';
|
import 'base/common.dart';
|
||||||
|
@ -225,82 +222,6 @@ abstract class Device {
|
||||||
'${getNameForTargetPlatform(device.platform)}$supportIndicator');
|
'${getNameForTargetPlatform(device.platform)}$supportIndicator');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<rpc.Peer> _connectToObservatory(int observatoryPort) async {
|
|
||||||
Uri uri = new Uri(scheme: 'ws', host: '127.0.0.1', port: observatoryPort, path: 'ws');
|
|
||||||
WebSocket ws = await WebSocket.connect(uri.toString());
|
|
||||||
rpc.Peer peer = new rpc.Peer(new IOWebSocketChannel(ws));
|
|
||||||
peer.listen();
|
|
||||||
return peer;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Null> startTracing(int observatoryPort) async {
|
|
||||||
rpc.Client client;
|
|
||||||
try {
|
|
||||||
client = await _connectToObservatory(observatoryPort);
|
|
||||||
} catch (e) {
|
|
||||||
printError('Error connecting to observatory: $e');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.sendRequest('_setVMTimelineFlags',
|
|
||||||
<String, dynamic>{'recordedStreams': <String>['Compiler', 'Dart', 'Embedder', 'GC']}
|
|
||||||
);
|
|
||||||
await client.sendRequest('_clearVMTimeline');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stops tracing, optionally waiting
|
|
||||||
Future<Map<String, dynamic>> stopTracingAndDownloadTimeline(int observatoryPort, {bool waitForFirstFrame: false}) async {
|
|
||||||
rpc.Peer peer;
|
|
||||||
try {
|
|
||||||
peer = await _connectToObservatory(observatoryPort);
|
|
||||||
} catch (e) {
|
|
||||||
printError('Error connecting to observatory: $e');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchTimeline() async {
|
|
||||||
return await peer.sendRequest('_getVMTimeline');
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> timeline;
|
|
||||||
|
|
||||||
if (!waitForFirstFrame) {
|
|
||||||
// Stop tracing immediately and get the timeline
|
|
||||||
await peer.sendRequest('_setVMTimelineFlags', <String, dynamic>{'recordedStreams': '[]'});
|
|
||||||
timeline = await fetchTimeline();
|
|
||||||
} else {
|
|
||||||
Completer<Null> whenFirstFrameRendered = new Completer<Null>();
|
|
||||||
peer.registerMethod('streamNotify', (rpc.Parameters params) {
|
|
||||||
Map<String, dynamic> data = params.asMap;
|
|
||||||
if (data['streamId'] == 'Timeline') {
|
|
||||||
List<Map<String, dynamic>> events = data['event']['timelineEvents'];
|
|
||||||
for (Map<String, dynamic> event in events) {
|
|
||||||
if (event['name'] == firstUsefulFrameEventName) {
|
|
||||||
whenFirstFrameRendered.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await peer.sendRequest('streamListen', <String, dynamic>{'streamId': 'Timeline'});
|
|
||||||
await whenFirstFrameRendered.future.timeout(
|
|
||||||
const Duration(seconds: 10),
|
|
||||||
onTimeout: () {
|
|
||||||
printError(
|
|
||||||
'Timed out waiting for the first frame event. Either the '
|
|
||||||
'application failed to start, or the event was missed because '
|
|
||||||
'"flutter run" took too long to subscribe to timeline events.'
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
timeline = await fetchTimeline();
|
|
||||||
await peer.sendRequest('_setVMTimelineFlags', <String, dynamic>{'recordedStreams': '[]'});
|
|
||||||
}
|
|
||||||
|
|
||||||
return timeline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DebuggingOptions {
|
class DebuggingOptions {
|
||||||
|
@ -332,7 +253,7 @@ class DebuggingOptions {
|
||||||
Future<int> findBestObservatoryPort() {
|
Future<int> findBestObservatoryPort() {
|
||||||
if (hasObservatoryPort)
|
if (hasObservatoryPort)
|
||||||
return new Future<int>.value(observatoryPort);
|
return new Future<int>.value(observatoryPort);
|
||||||
return findPreferredPort(observatoryPort ?? defaultObservatoryPort);
|
return findPreferredPort(observatoryPort ?? kDefaultObservatoryPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasDiagnosticPort => diagnosticPort != null;
|
bool get hasDiagnosticPort => diagnosticPort != null;
|
||||||
|
@ -340,7 +261,7 @@ class DebuggingOptions {
|
||||||
/// Return the user specified diagnostic port. If that isn't available,
|
/// Return the user specified diagnostic port. If that isn't available,
|
||||||
/// return [defaultObservatoryPort], or a port close to that one.
|
/// return [defaultObservatoryPort], or a port close to that one.
|
||||||
Future<int> findBestDiagnosticPort() {
|
Future<int> findBestDiagnosticPort() {
|
||||||
return findPreferredPort(diagnosticPort ?? defaultDiagnosticPort);
|
return findPreferredPort(diagnosticPort ?? kDefaultDiagnosticPort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import '../build_info.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../flx.dart' as flx;
|
import '../flx.dart' as flx;
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
import '../service_protocol.dart';
|
import '../protocol_discovery.dart';
|
||||||
import 'mac.dart';
|
import 'mac.dart';
|
||||||
|
|
||||||
const String _xcrunPath = '/usr/bin/xcrun';
|
const String _xcrunPath = '/usr/bin/xcrun';
|
||||||
|
@ -395,20 +395,17 @@ class IOSSimulator extends Device {
|
||||||
RegExp versionExp = new RegExp(r'iPhone ([0-9])+');
|
RegExp versionExp = new RegExp(r'iPhone ([0-9])+');
|
||||||
Match match = versionExp.firstMatch(name);
|
Match match = versionExp.firstMatch(name);
|
||||||
|
|
||||||
if (match == null) {
|
// Not an iPhone. All available non-iPhone simulators are compatible.
|
||||||
// Not an iPhone. All available non-iPhone simulators are compatible.
|
if (match == null)
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
if (int.parse(match.group(1)) > 5) {
|
// iPhones 6 and above are always fine.
|
||||||
// iPhones 6 and above are always fine.
|
if (int.parse(match.group(1)) > 5)
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
// The 's' subtype of 5 is compatible.
|
// The 's' subtype of 5 is compatible.
|
||||||
if (name.contains('iPhone 5s')) {
|
if (name.contains('iPhone 5s'))
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
_supportMessage = "The simulator version is too old. Choose an iPhone 5s or above.";
|
_supportMessage = "The simulator version is too old. Choose an iPhone 5s or above.";
|
||||||
return false;
|
return false;
|
||||||
|
@ -447,12 +444,10 @@ class IOSSimulator extends Device {
|
||||||
if (!(await _setupUpdatedApplicationBundle(app)))
|
if (!(await _setupUpdatedApplicationBundle(app)))
|
||||||
return new LaunchResult.failed();
|
return new LaunchResult.failed();
|
||||||
|
|
||||||
ServiceProtocolDiscovery observatoryDiscovery;
|
ProtocolDiscovery observatoryDiscovery;
|
||||||
|
|
||||||
if (debuggingOptions.debuggingEnabled) {
|
if (debuggingOptions.debuggingEnabled)
|
||||||
observatoryDiscovery = new ServiceProtocolDiscovery(
|
observatoryDiscovery = new ProtocolDiscovery(logReader, ProtocolDiscovery.kObservatoryService);
|
||||||
logReader, ServiceProtocolDiscovery.kObservatoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare launch arguments.
|
// Prepare launch arguments.
|
||||||
List<String> args = <String>[
|
List<String> args = <String>[
|
||||||
|
|
130
packages/flutter_tools/lib/src/observatory.dart
Normal file
130
packages/flutter_tools/lib/src/observatory.dart
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// 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:json_rpc_2/json_rpc_2.dart' as rpc;
|
||||||
|
import 'package:web_socket_channel/io.dart';
|
||||||
|
|
||||||
|
class Observatory {
|
||||||
|
Observatory._(this.peer, this.port) {
|
||||||
|
peer.registerMethod('streamNotify', (rpc.Parameters event) {
|
||||||
|
_handleStreamNotify(event.asMap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Observatory> connect(int port) async {
|
||||||
|
Uri uri = new Uri(scheme: 'ws', host: '127.0.0.1', port: port, path: 'ws');
|
||||||
|
WebSocket ws = await WebSocket.connect(uri.toString());
|
||||||
|
rpc.Peer peer = new rpc.Peer(new IOWebSocketChannel(ws));
|
||||||
|
peer.listen();
|
||||||
|
return new Observatory._(peer, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
final rpc.Peer peer;
|
||||||
|
final int port;
|
||||||
|
|
||||||
|
Map<String, StreamController<Event>> _eventControllers = <String, StreamController<Event>>{};
|
||||||
|
|
||||||
|
bool get isClosed => peer.isClosed;
|
||||||
|
Future<Null> get done => peer.done;
|
||||||
|
|
||||||
|
// Events
|
||||||
|
|
||||||
|
// IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
|
||||||
|
Stream<Event> get onIsolateEvent => _getEventController('Isolate').stream;
|
||||||
|
Stream<Event> get onTimelineEvent => _getEventController('Timeline').stream;
|
||||||
|
|
||||||
|
// Listen for a specific event name.
|
||||||
|
Stream<Event> onEvent(String streamName) => _getEventController(streamName).stream;
|
||||||
|
|
||||||
|
StreamController<Event> _getEventController(String eventName) {
|
||||||
|
StreamController<Event> controller = _eventControllers[eventName];
|
||||||
|
if (controller == null) {
|
||||||
|
controller = new StreamController<Event>.broadcast();
|
||||||
|
_eventControllers[eventName] = controller;
|
||||||
|
}
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleStreamNotify(Map<String, dynamic> data) {
|
||||||
|
Event event = new Event(data['event']);
|
||||||
|
_getEventController(data['streamId']).add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requests
|
||||||
|
|
||||||
|
Future<Response> sendRequest(String method, [Map<String, dynamic> args]) {
|
||||||
|
return peer.sendRequest(method, args).then((dynamic result) => new Response(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> streamListen(String streamId) {
|
||||||
|
return sendRequest('streamListen', <String, dynamic>{
|
||||||
|
'streamId': streamId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<VM> getVM() {
|
||||||
|
return peer.sendRequest('getVM').then((dynamic result) {
|
||||||
|
return new VM(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> isolateReload(String isolateId) {
|
||||||
|
return sendRequest('isolateReload', <String, dynamic>{
|
||||||
|
'isolateId': isolateId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> clearVMTimeline() => sendRequest('_clearVMTimeline');
|
||||||
|
|
||||||
|
Future<Response> setVMTimelineFlags(List<String> recordedStreams) {
|
||||||
|
assert(recordedStreams != null);
|
||||||
|
|
||||||
|
return sendRequest('_setVMTimelineFlags', <String, dynamic> {
|
||||||
|
'recordedStreams': recordedStreams
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> getVMTimeline() => sendRequest('_getVMTimeline');
|
||||||
|
|
||||||
|
// Flutter extension methods.
|
||||||
|
|
||||||
|
Future<Response> flutterExit(String isolateId) {
|
||||||
|
return peer.sendRequest('ext.flutter.exit', <String, dynamic>{
|
||||||
|
'isolateId': isolateId
|
||||||
|
}).then((dynamic result) => new Response(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Response {
|
||||||
|
Response(this.response);
|
||||||
|
|
||||||
|
final Map<String, dynamic> response;
|
||||||
|
|
||||||
|
dynamic operator[](String key) => response[key];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => response.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
class VM extends Response {
|
||||||
|
VM(Map<String, dynamic> response) : super(response);
|
||||||
|
|
||||||
|
List<dynamic> get isolates => response['isolates'];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
Event(this.event);
|
||||||
|
|
||||||
|
final Map<String, dynamic> event;
|
||||||
|
|
||||||
|
String get kind => event['kind'];
|
||||||
|
|
||||||
|
dynamic operator[](String key) => event[key];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => event.toString();
|
||||||
|
}
|
|
@ -7,9 +7,9 @@ import 'dart:async';
|
||||||
import 'device.dart';
|
import 'device.dart';
|
||||||
|
|
||||||
/// Discover service protocol ports on devices.
|
/// Discover service protocol ports on devices.
|
||||||
class ServiceProtocolDiscovery {
|
class ProtocolDiscovery {
|
||||||
/// [logReader] - a [DeviceLogReader] to look for service messages in.
|
/// [logReader] - a [DeviceLogReader] to look for service messages in.
|
||||||
ServiceProtocolDiscovery(DeviceLogReader logReader, String serviceName)
|
ProtocolDiscovery(DeviceLogReader logReader, String serviceName)
|
||||||
: _logReader = logReader, _serviceName = serviceName {
|
: _logReader = logReader, _serviceName = serviceName {
|
||||||
assert(_logReader != null);
|
assert(_logReader != null);
|
||||||
_subscription = _logReader.logLines.listen(_onLine);
|
_subscription = _logReader.logLines.listen(_onLine);
|
|
@ -23,8 +23,8 @@ import 'install_test.dart' as install_test;
|
||||||
import 'listen_test.dart' as listen_test;
|
import 'listen_test.dart' as listen_test;
|
||||||
import 'logs_test.dart' as logs_test;
|
import 'logs_test.dart' as logs_test;
|
||||||
import 'os_utils_test.dart' as os_utils_test;
|
import 'os_utils_test.dart' as os_utils_test;
|
||||||
|
import 'protocol_discovery_test.dart' as protocol_discovery_test;
|
||||||
import 'run_test.dart' as run_test;
|
import 'run_test.dart' as run_test;
|
||||||
import 'service_protocol_test.dart' as service_protocol_test;
|
|
||||||
import 'stop_test.dart' as stop_test;
|
import 'stop_test.dart' as stop_test;
|
||||||
import 'toolchain_test.dart' as toolchain_test;
|
import 'toolchain_test.dart' as toolchain_test;
|
||||||
import 'trace_test.dart' as trace_test;
|
import 'trace_test.dart' as trace_test;
|
||||||
|
@ -47,8 +47,8 @@ void main() {
|
||||||
listen_test.main();
|
listen_test.main();
|
||||||
logs_test.main();
|
logs_test.main();
|
||||||
os_utils_test.main();
|
os_utils_test.main();
|
||||||
|
protocol_discovery_test.main();
|
||||||
run_test.main();
|
run_test.main();
|
||||||
service_protocol_test.main();
|
|
||||||
stop_test.main();
|
stop_test.main();
|
||||||
toolchain_test.main();
|
toolchain_test.main();
|
||||||
trace_test.main();
|
trace_test.main();
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_tools/src/service_protocol.dart';
|
import 'package:flutter_tools/src/protocol_discovery.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'src/mocks.dart';
|
import 'src/mocks.dart';
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ void main() {
|
||||||
group('service_protocol', () {
|
group('service_protocol', () {
|
||||||
test('Discovery Heartbeat', () async {
|
test('Discovery Heartbeat', () async {
|
||||||
MockDeviceLogReader logReader = new MockDeviceLogReader();
|
MockDeviceLogReader logReader = new MockDeviceLogReader();
|
||||||
ServiceProtocolDiscovery discoverer =
|
ProtocolDiscovery discoverer =
|
||||||
new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService);
|
new ProtocolDiscovery(logReader, ProtocolDiscovery.kObservatoryService);
|
||||||
|
|
||||||
// Get next port future.
|
// Get next port future.
|
||||||
Future<int> nextPort = discoverer.nextPort();
|
Future<int> nextPort = discoverer.nextPort();
|
Loading…
Reference in a new issue