Commit 8451b669 authored by Jason Simmons's avatar Jason Simmons

Collect trace data through the observatory HTTP interface (#3393)

parent 61605a9d
......@@ -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(<String>[
'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<String> 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(<String>[
'shell',
'am',
'broadcast',
'-a',
'${apk.id}.TRACING_STOP'
]));
Future<rpc.Client> _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<String> args = <String>['logcat', '-d'];
if (beforeStop != null)
args.addAll(<String>['-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<Null> 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);
// 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<String> catCommand = adbCommandForDevice(
<String>['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(
<String>['shell', 'run-as', apk.id, 'rm', tracePath]
));
return localPath;
await client.sendRequest('_setVMTimelineFlags',
{'recordedStreams': ['Compiler', 'Dart', 'Embedder', 'GC']}
);
await client.sendRequest('_clearVMTimeline');
}
Future<String> 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<String, dynamic> response = await client.sendRequest('_getVMTimeline');
List<dynamic> traceEvents = response['traceEvents'];
IOSink sink = localFile.openWrite();
Stream<Object> streamIn = new Stream<Object>.fromIterable(<Object>[traceEvents]);
Stream<List<int>> streamOut = new JsonUtf8Encoder().bind(streamIn);
await sink.addStream(streamOut);
await sink.close();
return path.basename(localFile.path);
}
@override
......
......@@ -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<int> 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<Null>.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<Null> _stopTracing(AndroidDevice android, AndroidApk androidApp) async {
String tracePath = await android.stopTracing(androidApp, outPath: argResults['out']);
Future<Null> _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 {
......
......@@ -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
......
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