blob: f9b742133a449ac6c1ea05e342178abd27efff60 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. 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:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'driver.dart';
import 'input_converter.dart';
import 'operation.dart';
/**
* Launch and interact with the analysis server.
*/
main(List<String> rawArgs) {
Logger logger = new Logger('Performance Measurement Client');
logger.onRecord.listen((LogRecord rec) {
print(rec.message);
});
PerfArgs args = parseArgs(rawArgs);
Driver driver = new Driver(diagnosticPort: args.diagnosticPort);
Stream<Operation> stream = openInput(args);
StreamSubscription<Operation> subscription;
subscription = stream.listen((Operation op) {
Future future = driver.perform(op);
if (future != null) {
logger.log(Level.FINE, 'pausing operations for ${op.runtimeType}');
subscription.pause(future.then((_) {
logger.log(Level.FINE, 'resuming operations');
}));
}
}, onDone: () {
subscription.cancel();
driver.stopServer(SHUTDOWN_TIMEOUT);
}, onError: (e, s) {
subscription.cancel();
logger.log(Level.SEVERE, '$e\n$s');
driver.stopServer(SHUTDOWN_TIMEOUT);
});
driver.runComplete.then((Results results) {
results.printResults();
}).whenComplete(() {
return subscription.cancel();
});
}
const DIAGNOSTIC_PORT_OPTION = 'diagnosticPort';
const HELP_CMDLINE_OPTION = 'help';
const INPUT_CMDLINE_OPTION = 'input';
const MAP_OPTION = 'map';
/**
* The amount of time to give the server to respond to a shutdown request
* before forcibly terminating it.
*/
const Duration SHUTDOWN_TIMEOUT = const Duration(seconds: 25);
const TMP_SRC_DIR_OPTION = 'tmpSrcDir';
const VERBOSE_CMDLINE_OPTION = 'verbose';
const VERY_VERBOSE_CMDLINE_OPTION = 'vv';
ArgParser _argParser;
ArgParser get argParser {
_argParser = new ArgParser();
_argParser.addOption(INPUT_CMDLINE_OPTION,
abbr: 'i',
help: '<filePath>\n'
'The input file specifying how this client should interact with the server.\n'
'If the input file name is "stdin", then the instructions are read from standard input.');
_argParser.addMultiOption(MAP_OPTION,
abbr: 'm',
splitCommas: false,
help: '<oldSrcPath>,<newSrcPath>\n'
'This option defines a mapping from the original source directory <oldSrcPath>\n'
'when the instrumentation or log file was generated\n'
'to the target source directory <newSrcPath> used during performance testing.\n'
'Multiple mappings can be specified.\n'
'WARNING: The contents of the target directory will be modified');
_argParser.addOption(TMP_SRC_DIR_OPTION,
abbr: 't',
help: '<dirPath>\n'
'The temporary directory containing source used during performance measurement.\n'
'WARNING: The contents of the target directory will be modified');
_argParser.addOption(DIAGNOSTIC_PORT_OPTION,
abbr: 'd',
help: 'localhost port on which server will provide diagnostic web pages');
_argParser.addFlag(VERBOSE_CMDLINE_OPTION,
abbr: 'v', help: 'Verbose logging', negatable: false);
_argParser.addFlag(VERY_VERBOSE_CMDLINE_OPTION,
help: 'Extra verbose logging', negatable: false);
_argParser.addFlag(HELP_CMDLINE_OPTION,
abbr: 'h', help: 'Print this help information', negatable: false);
return _argParser;
}
/**
* Open and return the input stream specifying how this client
* should interact with the analysis server.
*/
Stream<Operation> openInput(PerfArgs args) {
var logger = new Logger('openInput');
Stream<List<int>> inputRaw;
if (args.inputPath == 'stdin') {
inputRaw = stdin;
} else {
inputRaw = new File(args.inputPath).openRead();
}
for (PathMapEntry entry in args.srcPathMap.entries) {
logger.log(
Level.INFO,
'mapping source path\n'
' from ${entry.oldSrcPrefix}\n to ${entry.newSrcPrefix}');
}
logger.log(Level.INFO, 'tmpSrcDir: ${args.tmpSrcDirPath}');
return inputRaw
.cast<List<int>>()
.transform(systemEncoding.decoder)
.transform(new LineSplitter())
.transform(new InputConverter(args.tmpSrcDirPath, args.srcPathMap));
}
/**
* Parse the command line arguments.
*/
PerfArgs parseArgs(List<String> rawArgs) {
ArgResults args;
PerfArgs perfArgs = new PerfArgs();
try {
args = argParser.parse(rawArgs);
} on Exception catch (e) {
print(e);
printHelp();
exit(1);
}
bool showHelp = args[HELP_CMDLINE_OPTION] || args.rest.isNotEmpty;
bool isMissing(key) => args[key] == null || args[key].isEmpty;
perfArgs.inputPath = args[INPUT_CMDLINE_OPTION];
if (isMissing(INPUT_CMDLINE_OPTION)) {
print('missing $INPUT_CMDLINE_OPTION argument');
showHelp = true;
}
for (String pair in args[MAP_OPTION]) {
if (pair is String) {
int index = pair.indexOf(',');
if (index != -1 && pair.indexOf(',', index + 1) == -1) {
String oldSrcPrefix = _withTrailingSeparator(pair.substring(0, index));
String newSrcPrefix = _withTrailingSeparator(pair.substring(index + 1));
if (new Directory(newSrcPrefix).existsSync()) {
perfArgs.srcPathMap.add(oldSrcPrefix, newSrcPrefix);
continue;
}
}
}
print('must specifiy $MAP_OPTION <oldSrcPath>,<newSrcPath>');
showHelp = true;
}
perfArgs.tmpSrcDirPath = _withTrailingSeparator(args[TMP_SRC_DIR_OPTION]);
if (isMissing(TMP_SRC_DIR_OPTION)) {
print('missing $TMP_SRC_DIR_OPTION argument');
showHelp = true;
}
String portText = args[DIAGNOSTIC_PORT_OPTION];
if (portText != null) {
if (int.tryParse(portText) == null) {
print('invalid $DIAGNOSTIC_PORT_OPTION: $portText');
showHelp = true;
} else {
perfArgs.diagnosticPort = int.tryParse(portText);
}
}
if (args[VERY_VERBOSE_CMDLINE_OPTION] || rawArgs.contains('-vv')) {
Logger.root.level = Level.FINE;
} else if (args[VERBOSE_CMDLINE_OPTION]) {
Logger.root.level = Level.INFO;
} else {
Logger.root.level = Level.WARNING;
}
if (showHelp) {
printHelp();
exit(1);
}
return perfArgs;
}
void printHelp() {
print('');
print('Launch and interact with the AnalysisServer');
print('');
print(argParser.usage);
}
/**
* Ensure that the given path has a trailing separator
*/
String _withTrailingSeparator(String dirPath) {
if (dirPath != null && dirPath.length > 4) {
if (!dirPath.endsWith(path.separator)) {
return '$dirPath${path.separator}';
}
}
return dirPath;
}
/**
* The performance measurement arguments specified on the command line.
*/
class PerfArgs {
/**
* The file path of the instrumentation or log file
* used to drive performance measurement,
* or 'stdin' if this information should be read from standard input.
*/
String inputPath;
/**
* A mapping from the original source directory
* when the instrumentation or log file was generated
* to the target source directory used during performance testing.
*/
final PathMap srcPathMap = new PathMap();
/**
* The temporary directory containing source used during performance measurement.
*/
String tmpSrcDirPath;
/**
* The diagnostic port for Analysis Server or `null` if none.
*/
int diagnosticPort;
}