diff --git a/packages/flutter_tools/bin/sky_tools.dart b/packages/flutter_tools/bin/sky_tools.dart index a0e2d71db2..a35d3e3f7c 100644 --- a/packages/flutter_tools/bin/sky_tools.dart +++ b/packages/flutter_tools/bin/sky_tools.dart @@ -18,6 +18,7 @@ import 'package:sky_tools/src/logs.dart'; import 'package:sky_tools/src/run_mojo.dart'; import 'package:sky_tools/src/start.dart'; import 'package:sky_tools/src/stop.dart'; +import 'package:sky_tools/src/trace.dart'; class FlutterCommandRunner extends CommandRunner { FlutterCommandRunner() @@ -155,5 +156,6 @@ void main(List args) { ..addCommand(new RunMojoCommand()) ..addCommand(new StartCommand()) ..addCommand(new StopCommand()) + ..addCommand(new TraceCommand()) ..run(args); } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index f783b121bb..e75d17744f 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -339,6 +339,62 @@ class AndroidDevice extends _Device { ], prefix: 'ANDROID: '); } + void startTracing(AndroidApk apk) { + runCheckedSync([ + adbPath, + 'shell', + 'am', + 'broadcast', + '-a', + '${apk.appPackageID}.TRACING_START' + ]); + } + + String stopTracing(AndroidApk apk) { + clearLogs(); + runCheckedSync([ + adbPath, + 'shell', + 'am', + 'broadcast', + '-a', + '${apk.appPackageID}.TRACING_STOP' + ]); + + RegExp traceRegExp = new RegExp(r'Saving trace to (\S+)', multiLine: true); + RegExp completeRegExp = new RegExp(r'Trace complete', multiLine: true); + + String tracePath = null; + bool isComplete = false; + while (!isComplete) { + String logs = runSync([adbPath, 'logcat', '-d']); + Match fileMatch = traceRegExp.firstMatch(logs); + if (fileMatch[1] != null) { + tracePath = fileMatch[1]; + } + isComplete = completeRegExp.hasMatch(logs); + } + + if (tracePath != null) { + // adb root exits with 0 even if the command fails, + // so check the output string + String output = runSync([adbPath, 'root']); + if (new RegExp(r'.*cannot run as root.*').hasMatch(output)) { + _logging + .severe('Unable to download trace "${path.basename(tracePath)}"\n' + 'You need to be able to run adb as root ' + 'on your android device'); + return null; + } + runSync([adbPath, 'pull', tracePath]); + runSync([adbPath, 'shell', 'rm', tracePath]); + return path.basename(tracePath); + } + _logging.warning('No trace file detected. ' + 'Did you remember to start the trace before stopping it?'); + return null; + } + @override bool isConnected() => _hasValidAndroid; } diff --git a/packages/flutter_tools/lib/src/trace.dart b/packages/flutter_tools/lib/src/trace.dart new file mode 100644 index 0000000000..8d307f36cd --- /dev/null +++ b/packages/flutter_tools/lib/src/trace.dart @@ -0,0 +1,69 @@ +// Copyright 2015 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. + +library sky_tools.trace; + +import 'dart:async'; + +import 'package:args/command_runner.dart'; +import 'package:logging/logging.dart'; +import 'package:sky_tools/src/application_package.dart'; +import 'package:sky_tools/src/device.dart'; + +final Logger _logging = new Logger('sky_tools.trace'); + +class TraceCommand extends Command { + final name = 'trace'; + final description = 'Start and stop tracing a running Flutter app ' + '(Android only, requires root).\n' + 'To start a trace, wait, and then stop the trace, don\'t set any flags ' + 'except (optionally) duration.\n' + 'Otherwise, specify either start or stop to manually control the trace.'; + AndroidDevice android = null; + + TraceCommand([this.android]) { + argParser.addFlag('start', negatable: false, help: 'Start tracing.'); + argParser.addFlag('stop', negatable: false, help: 'Stop tracing.'); + argParser.addOption('duration', + defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.'); + if (android == null) { + android = new AndroidDevice(); + } + } + + @override + Future run() async { + if (!android.isConnected()) { + _logging.warning('No device connected, so no trace was completed.'); + return 1; + } + Map packages = + ApplicationPackageFactory.getAvailableApplicationPackages(); + ApplicationPackage androidApp = packages[BuildPlatform.android]; + + 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. + android.startTracing(androidApp); + await new Future.delayed( + new Duration(seconds: int.parse(argResults['duration'])), + () => _stopTracing(androidApp)); + } else if (argResults['stop']) { + _stopTracing(androidApp); + } else { + android.startTracing(androidApp); + } + return 0; + } + + void _stopTracing(AndroidApk androidApp) { + String tracePath = android.stopTracing(androidApp); + if (tracePath == null) { + _logging.warning('No trace file saved.'); + } else { + _logging.warning('Trace file saved to $tracePath'); + } + } +} diff --git a/packages/flutter_tools/test/trace_test.dart b/packages/flutter_tools/test/trace_test.dart new file mode 100644 index 0000000000..13059813a2 --- /dev/null +++ b/packages/flutter_tools/test/trace_test.dart @@ -0,0 +1,33 @@ +// Copyright 2015 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. + +library trace_test; + +import 'package:args/command_runner.dart'; +import 'package:mockito/mockito.dart'; +import 'package:sky_tools/src/application_package.dart'; +import 'package:sky_tools/src/trace.dart'; +import 'package:test/test.dart'; + +import 'src/common.dart'; + +main() => defineTests(); + +defineTests() { + group('trace', () { + test('returns 1 when no Android device is connected', () { + ApplicationPackageFactory.srcPath = './'; + ApplicationPackageFactory.setBuildPath( + BuildType.prebuilt, BuildPlatform.android, './'); + + MockAndroidDevice android = new MockAndroidDevice(); + when(android.isConnected()).thenReturn(false); + TraceCommand command = new TraceCommand(android); + + CommandRunner runner = new CommandRunner('test_flutter', '') + ..addCommand(command); + runner.run(['trace']).then((int code) => expect(code, equals(1))); + }); + }); +}