diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 9f4ab5aeb6..f21d60a3dc 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -6,13 +6,16 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:path/path.dart' as path; +import 'package:web_socket_channel/io.dart'; import '../android/android_sdk.dart'; import '../application_package.dart'; import '../base/common.dart'; import '../base/os.dart'; import '../base/process.dart'; +import '../base/utils.dart'; import '../build_configuration.dart'; import '../device.dart'; import '../flx.dart' as flx; @@ -348,16 +351,6 @@ class AndroidDevice extends Device { return _portForwarder; } - void startTracing(AndroidApk apk) { - runCheckedSync(adbCommandForDevice([ - 'shell', - 'am', - 'broadcast', - '-a', - '${apk.id}.TRACING_START' - ])); - } - /// Return the most recent timestamp in the Android log or `null` if there is /// no available timestamp. The format can be passed to logcat's -T option. String get lastLogcatTimestamp { @@ -370,59 +363,57 @@ class AndroidDevice extends Device { return timeMatch?.group(0); } - Future stopTracing(AndroidApk apk, { String outPath }) async { - // Workaround for logcat -c not always working: - // http://stackoverflow.com/questions/25645012/logcat-on-android-l-not-clearing-after-unplugging-and-reconnecting - String beforeStop = lastLogcatTimestamp; - runCheckedSync(adbCommandForDevice([ - 'shell', - 'am', - 'broadcast', - '-a', - '${apk.id}.TRACING_STOP' - ])); + Future _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.Client client = new rpc.Client(new IOWebSocketChannel(ws)); + client.listen(); + return client; + } - RegExp traceRegExp = new RegExp(r'Saving trace to (\S+)', multiLine: true); - RegExp completeRegExp = new RegExp(r'Trace complete', multiLine: true); - - String tracePath; - bool isComplete = false; - while (!isComplete) { - List args = ['logcat', '-d']; - if (beforeStop != null) - args.addAll(['-T', beforeStop]); - String logs = runCheckedSync(adbCommandForDevice(args)); - Match fileMatch = traceRegExp.firstMatch(logs); - if (fileMatch != null && fileMatch[1] != null) { - tracePath = fileMatch[1]; - } - isComplete = completeRegExp.hasMatch(logs); + Future startTracing(AndroidApk apk, int observatoryPort) async { + rpc.Client client; + try { + client = await _connectToObservatory(observatoryPort); + } catch (e) { + printError('Error connecting to observatory: $e'); + return; } - if (tracePath != null) { - String localPath = (outPath != null) ? outPath : path.basename(tracePath); + await client.sendRequest('_setVMTimelineFlags', + {'recordedStreams': ['Compiler', 'Dart', 'Embedder', 'GC']} + ); + await client.sendRequest('_clearVMTimeline'); + } - // Run cat via ADB to print the captured trace file. (adb pull will be unable - // to access the file if it does not have root permissions) - IOSink catOutput = new File(localPath).openWrite(); - List catCommand = adbCommandForDevice( - ['shell', 'run-as', apk.id, 'cat', tracePath] - ); - Process catProcess = await Process.start(catCommand[0], - catCommand.getRange(1, catCommand.length).toList()); - catProcess.stdout.pipe(catOutput); - int exitCode = await catProcess.exitCode; - if (exitCode != 0) - throw 'Error code $exitCode returned when running ${catCommand.join(" ")}'; - - runSync(adbCommandForDevice( - ['shell', 'run-as', apk.id, 'rm', tracePath] - )); - return localPath; + Future stopTracing(AndroidApk apk, int observatoryPort, String outPath) async { + rpc.Client client; + try { + client = await _connectToObservatory(observatoryPort); + } catch (e) { + printError('Error connecting to observatory: $e'); + return null; } - printError('No trace file detected. ' - 'Did you remember to start the trace before stopping it?'); - return null; + + await client.sendRequest('_setVMTimelineFlags', {'recordedStreams': '[]'}); + + File localFile; + if (outPath != null) { + localFile = new File(outPath); + } else { + localFile = getUniqueFile(Directory.current, 'trace', 'json'); + } + + Map response = await client.sendRequest('_getVMTimeline'); + List traceEvents = response['traceEvents']; + + IOSink sink = localFile.openWrite(); + Stream streamIn = new Stream.fromIterable([traceEvents]); + Stream> streamOut = new JsonUtf8Encoder().bind(streamIn); + await sink.addStream(streamOut); + await sink.close(); + + return path.basename(localFile.path); } @override diff --git a/packages/flutter_tools/lib/src/commands/trace.dart b/packages/flutter_tools/lib/src/commands/trace.dart index 15aecede35..792e064678 100644 --- a/packages/flutter_tools/lib/src/commands/trace.dart +++ b/packages/flutter_tools/lib/src/commands/trace.dart @@ -6,6 +6,7 @@ import 'dart:async'; import '../android/android_device.dart'; import '../application_package.dart'; +import '../base/common.dart'; import '../globals.dart'; import '../runner/flutter_command.dart'; @@ -16,6 +17,9 @@ class TraceCommand extends FlutterCommand { argParser.addOption('out', help: 'Specify the path of the saved trace file.'); argParser.addOption('duration', defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.'); + argParser.addOption('debug-port', + defaultsTo: observatoryDefaultPort.toString(), + help: 'Local port where the observatory is listening.'); } @override @@ -40,26 +44,27 @@ class TraceCommand extends FlutterCommand { Future runInProject() async { ApplicationPackage androidApp = applicationPackages.android; AndroidDevice device = deviceForCommand; + int observatoryPort = int.parse(argResults['debug-port']); if ((!argResults['start'] && !argResults['stop']) || (argResults['start'] && argResults['stop'])) { // Setting neither flags or both flags means do both commands and wait // duration seconds in between. - device.startTracing(androidApp); + await device.startTracing(androidApp, observatoryPort); await new Future.delayed( new Duration(seconds: int.parse(argResults['duration'])), - () => _stopTracing(device, androidApp) + () => _stopTracing(device, androidApp, observatoryPort) ); } else if (argResults['stop']) { - await _stopTracing(device, androidApp); + await _stopTracing(device, androidApp, observatoryPort); } else { - device.startTracing(androidApp); + await device.startTracing(androidApp, observatoryPort); } return 0; } - Future _stopTracing(AndroidDevice android, AndroidApk androidApp) async { - String tracePath = await android.stopTracing(androidApp, outPath: argResults['out']); + Future _stopTracing(AndroidDevice android, AndroidApk androidApp, int observatoryPort) async { + String tracePath = await android.stopTracing(androidApp, observatoryPort, argResults['out']); if (tracePath == null) { printError('No trace file saved.'); } else { diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 7637f0faa7..ad659985b5 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -13,14 +13,16 @@ dependencies: crypto: 0.9.2 file: ^0.1.0 http: ^0.11.3 + json_rpc_2: ^2.0.0 json_schema: ^1.0.3 mustache4dart: ^1.0.0 package_config: ^0.1.3 path: ^1.3.0 pub_semver: ^1.0.0 stack_trace: ^1.4.0 - yaml: ^2.1.3 + web_socket_channel: ^1.0.0 xml: ^2.4.1 + yaml: ^2.1.3 flx: path: ../flx