Add analytics to analyzer-cli and analysis server.

BUG=
R=brianwilkerson@google.com, scheglov@google.com, zra@google.com

Review-Url: https://codereview.chromium.org/2963323002 .
This commit is contained in:
Devon Carew 2017-07-05 23:29:45 -07:00
parent 2bfde5a34e
commit d4abd49df7
33 changed files with 394 additions and 89 deletions

2
DEPS
View file

@ -122,7 +122,7 @@ vars = {
"test_tag": "@0.12.18+1",
"tuple_tag": "@v1.0.1",
"typed_data_tag": "@1.1.3",
"usage_tag": "@3.2.0+1",
"usage_tag": "@3.3.0",
"utf_tag": "@0.9.0+3",
"watcher_tag": "@0.9.7+3",
"web_components_rev": "@6349e09f9118dce7ae1b309af5763745e25a9d61",

View file

@ -11,14 +11,17 @@ dart_package("analysis_server") {
deps = [
"//dart/pkg/analyzer",
"//dart/pkg/telemetry",
"//dart/third_party/pkg/linter",
"//third_party/dart-pkg/pub/args",
"//third_party/dart-pkg/pub/dart_style",
"//third_party/dart-pkg/pub/intl",
"//third_party/dart-pkg/pub/isolate",
"//third_party/dart-pkg/pub/logging",
"//third_party/dart-pkg/pub/package_config",
"//third_party/dart-pkg/pub/path",
"//third_party/dart-pkg/pub/plugin",
"//third_party/dart-pkg/pub/usage",
"//third_party/dart-pkg/pub/watcher",
"//third_party/dart-pkg/pub/yaml",
]

View file

@ -61,6 +61,8 @@ import 'package:front_end/src/base/performace_logger.dart';
import 'package:front_end/src/incremental/byte_store.dart';
import 'package:front_end/src/incremental/file_byte_store.dart';
import 'package:plugin/plugin.dart';
import 'package:telemetry/crash_reporting.dart';
import 'package:telemetry/telemetry.dart' as telemetry;
import 'package:watcher/watcher.dart';
typedef void OptionUpdater(AnalysisOptionsImpl options);
@ -778,6 +780,9 @@ class AnalysisServer {
new ServerErrorParams(fatal, message, buffer.toString())
.toNotification());
// send to crash reporting
options.crashReportSender?.sendReport(exception, stackTrace: stackTrace);
// remember the last few exceptions
if (exception is CaughtException) {
stackTrace ??= exception.stackTrace;
@ -904,8 +909,13 @@ class AnalysisServer {
return contextManager.isInAnalysisRoot(file);
}
void shutdown() {
Future<Null> shutdown() async {
running = false;
await options.analytics
?.waitForLastPing(timeout: new Duration(milliseconds: 200));
options.analytics?.close();
// Defer closing the channel and shutting down the instrumentation server so
// that the shutdown response can be sent and logged.
new Future(() {
@ -1043,16 +1053,30 @@ class AnalysisServer {
}
}
/**
* Various IDE options.
*/
class AnalysisServerOptions {
bool useAnalysisHighlight2 = false;
bool enableVerboseFlutterCompletions = false;
String fileReadMode = 'as-is';
String newAnalysisDriverLog;
String clientId;
String clientVersion;
// IDE options
bool enableVerboseFlutterCompletions = false;
/**
* The analytics instance; note, this object can be `null`, and should be
* accessed via a null-aware operator.
*/
telemetry.Analytics analytics;
/**
* The crash report sender instance; note, this object can be `null`, and
* should be accessed via a null-aware operator.
*/
CrashReportSender crashReportSender;
}
/**

View file

@ -278,6 +278,8 @@ class AnalysisDomainHandler extends AbstractRequestHandler {
* Implement the 'analysis.reanalyze' request.
*/
Response reanalyze(Request request) {
server.options.analytics?.sendEvent('analysis', 'reanalyze');
AnalysisReanalyzeParams params =
new AnalysisReanalyzeParams.fromRequest(request);
List<String> roots = params.roots;
@ -314,6 +316,10 @@ class AnalysisDomainHandler extends AbstractRequestHandler {
var params = new AnalysisSetAnalysisRootsParams.fromRequest(request);
List<String> includedPathList = params.included;
List<String> excludedPathList = params.excluded;
server.options.analytics?.sendEvent('analysis', 'setAnalysisRoots',
value: includedPathList.length);
// validate
for (String path in includedPathList) {
if (!server.isValidFilePath(path)) {

View file

@ -2,6 +2,8 @@
// 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 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
@ -39,7 +41,8 @@ class ServerDomainHandler implements RequestHandler {
} else if (requestName == SERVER_REQUEST_SET_SUBSCRIPTIONS) {
return setSubscriptions(request);
} else if (requestName == SERVER_REQUEST_SHUTDOWN) {
return shutdown(request);
shutdown(request);
return Response.DELAYED_RESPONSE;
}
} on RequestFailure catch (exception) {
return exception.response;
@ -63,9 +66,9 @@ class ServerDomainHandler implements RequestHandler {
/**
* Cleanly shutdown the analysis server.
*/
Response shutdown(Request request) {
server.shutdown();
Future<Null> shutdown(Request request) async {
await server.shutdown();
Response response = new ServerShutdownResult().toResponse(request.id);
return response;
server.sendResponse(response);
}
}

View file

@ -77,6 +77,8 @@ class EditDomainHandler extends AbstractRequestHandler {
}
Response format(Request request) {
server.options.analytics?.sendEvent('edit', 'format');
EditFormatParams params = new EditFormatParams.fromRequest(request);
String file = params.file;
@ -270,6 +272,8 @@ class EditDomainHandler extends AbstractRequestHandler {
}
Future getPostfixCompletion(Request request) async {
server.options.analytics?.sendEvent('edit', 'getPostfixCompletion');
var params = new EditGetPostfixCompletionParams.fromRequest(request);
SourceChange change;
@ -448,6 +452,8 @@ class EditDomainHandler extends AbstractRequestHandler {
}
Future<Null> organizeDirectives(Request request) async {
server.options.analytics?.sendEvent('edit', 'organizeDirectives');
var params = new EditOrganizeDirectivesParams.fromRequest(request);
// prepare file
String file = params.file;
@ -717,6 +723,12 @@ class _RefactoringManager {
EMPTY_PROBLEM_LIST, EMPTY_PROBLEM_LIST, EMPTY_PROBLEM_LIST);
// process the request
var params = new EditGetRefactoringParams.fromRequest(_request);
if (params.kind != null) {
server.options.analytics
?.sendEvent('refactor', params.kind.name.toLowerCase());
}
runZoned(() async {
await _init(params.kind, params.file, params.offset, params.length);
if (initStatus.hasFatalError) {

View file

@ -25,6 +25,8 @@ import 'package:args/args.dart';
import 'package:linter/src/rules.dart' as linter;
import 'package:plugin/manager.dart';
import 'package:plugin/plugin.dart';
import 'package:telemetry/crash_reporting.dart';
import 'package:telemetry/telemetry.dart' as telemetry;
/// Commandline argument parser. (Copied from analyzer/lib/options.dart)
/// TODO(pquitslund): replaces with a simple [ArgParser] instance
@ -125,6 +127,10 @@ class CommandLineParser {
String arg = args[i];
if (arg.startsWith('--') && arg.length > 2) {
String option = arg.substring(2);
// remove any leading 'no-'
if (option.startsWith('no-')) {
option = option.substring(3);
}
// strip the last '=value'
int equalsOffset = option.lastIndexOf('=');
if (equalsOffset != -1) {
@ -194,6 +200,16 @@ class Driver implements ServerStarter {
*/
static const String HELP_OPTION = "help";
/**
* The name of the flag used to configure reporting analytics.
*/
static const String ANALYTICS_FLAG = "analytics";
/**
* Suppress analytics for this session.
*/
static const String SUPPRESS_ANALYTICS_FLAG = "suppress-analytics";
/**
* The name of the option used to cause instrumentation to also be written to
* a local file.
@ -232,8 +248,6 @@ class Driver implements ServerStarter {
/**
* The path to the SDK.
* TODO(paulberry): get rid of this once the 'analysis.updateSdks' request is
* operational.
*/
static const String SDK_OPTION = "sdk";
@ -284,8 +298,43 @@ class Driver implements ServerStarter {
AnalysisServer start(List<String> arguments) {
CommandLineParser parser = _createArgParser();
ArgResults results = parser.parse(arguments, <String, String>{});
AnalysisServerOptions analysisServerOptions = new AnalysisServerOptions();
analysisServerOptions.useAnalysisHighlight2 =
results[USE_ANALYSIS_HIGHLIGHT2];
analysisServerOptions.fileReadMode = results[FILE_READ_MODE];
analysisServerOptions.newAnalysisDriverLog =
results[NEW_ANALYSIS_DRIVER_LOG];
analysisServerOptions.clientId = results[CLIENT_ID];
analysisServerOptions.clientVersion = results[CLIENT_VERSION];
analysisServerOptions.enableVerboseFlutterCompletions =
results[VERBOSE_FLUTTER_COMPLETIONS];
telemetry.Analytics analytics = telemetry.createAnalyticsInstance(
'UA-26406144-29', 'analysis-server',
disableForSession: results[SUPPRESS_ANALYTICS_FLAG]);
analysisServerOptions.analytics = analytics;
if (analysisServerOptions.clientId != null) {
// Record the client name as the application installer ID.
analytics.setSessionValue('aiid', analysisServerOptions.clientId);
}
if (analysisServerOptions.clientVersion != null) {
analytics.setSessionValue('cd1', analysisServerOptions.clientVersion);
}
// TODO(devoncarew): Replace with the real crash product ID.
analysisServerOptions.crashReportSender =
new CrashReportSender('Dart_analysis_server', analytics);
if (results.wasParsed(ANALYTICS_FLAG)) {
analytics.enabled = results[ANALYTICS_FLAG];
print(telemetry.createAnalyticsStatusMessage(analytics.enabled));
return null;
}
if (results[HELP_OPTION]) {
_printUsage(parser.parser);
_printUsage(parser.parser, analytics, fromHelp: true);
return null;
}
@ -298,25 +347,12 @@ class Driver implements ServerStarter {
} on FormatException {
print('Invalid port number: ${results[PORT_OPTION]}');
print('');
_printUsage(parser.parser);
_printUsage(parser.parser, analytics);
exitCode = 1;
return null;
}
}
AnalysisServerOptions analysisServerOptions = new AnalysisServerOptions();
analysisServerOptions.useAnalysisHighlight2 =
results[USE_ANALYSIS_HIGHLIGHT2];
analysisServerOptions.fileReadMode = results[FILE_READ_MODE];
analysisServerOptions.newAnalysisDriverLog =
results[NEW_ANALYSIS_DRIVER_LOG];
analysisServerOptions.clientId = results[CLIENT_ID];
analysisServerOptions.clientVersion = results[CLIENT_VERSION];
analysisServerOptions.enableVerboseFlutterCompletions =
results[VERBOSE_FLUTTER_COMPLETIONS];
//
// Process all of the plugins so that extensions are registered.
//
@ -361,14 +397,17 @@ class Driver implements ServerStarter {
new InstrumentationService(instrumentationServer);
instrumentationService.logVersion(
_readUuid(instrumentationService),
results[CLIENT_ID],
results[CLIENT_VERSION],
analysisServerOptions.clientId,
analysisServerOptions.clientVersion,
AnalysisServer.VERSION,
defaultSdk.sdkVersion);
AnalysisEngine.instance.instrumentationService = instrumentationService;
_DiagnosticServerImpl diagnosticServer = new _DiagnosticServerImpl();
// Ping analytics with our initial call.
analytics.sendScreenView('home');
//
// Create the sockets and start listening for requests.
//
@ -452,8 +491,7 @@ class Driver implements ServerStarter {
defaultsTo: false,
negatable: false);
parser.addOption(INSTRUMENTATION_LOG_FILE,
help:
"the path of the file to which instrumentation data will be written");
help: "write instrumentation data to the given file");
parser.addFlag(INTERNAL_PRINT_TO_CONSOLE,
help: "enable sending `print` output to the console",
defaultsTo: false,
@ -462,6 +500,10 @@ class Driver implements ServerStarter {
help: "set a destination for the new analysis driver's log");
parser.addFlag(VERBOSE_FLUTTER_COMPLETIONS,
help: "enable verbose code completion for Flutter (experimental)");
parser.addFlag(ANALYTICS_FLAG,
help: 'enable or disable sending analytics information to Google');
parser.addFlag(SUPPRESS_ANALYTICS_FLAG,
negatable: false, help: 'suppress analytics for this session');
parser.addOption(PORT_OPTION,
help: "the http diagnostic port on which the server provides"
" status and performance information");
@ -497,11 +539,21 @@ class Driver implements ServerStarter {
/**
* Print information about how to use the server.
*/
void _printUsage(ArgParser parser) {
void _printUsage(ArgParser parser, telemetry.Analytics analytics,
{bool fromHelp: false}) {
print('Usage: $BINARY_NAME [flags]');
print('');
print('Supported flags are:');
print(parser.usage);
// Print analytics status and information.
if (fromHelp) {
print('');
print(telemetry.analyticsNotice);
}
print('');
print(telemetry.createAnalyticsStatusMessage(analytics.enabled,
command: ANALYTICS_FLAG));
}
/**

View file

@ -16,6 +16,8 @@ dependencies:
package_config: '>=0.1.5 <2.0.0'
path: any
plugin: ^0.2.0
telemetry: ^0.0.1
usage: ^3.2.0+1
watcher: any
yaml: any
dev_dependencies:

View file

@ -20,9 +20,10 @@ import 'mocks.dart';
main() {
AnalysisServer server;
ServerDomainHandler handler;
MockServerChannel serverChannel;
setUp(() {
var serverChannel = new MockServerChannel();
serverChannel = new MockServerChannel();
var resourceProvider = new MemoryResourceProvider();
ExtensionManager manager = new ExtensionManager();
ServerPlugin serverPlugin = new ServerPlugin();
@ -72,12 +73,13 @@ main() {
});
});
test('shutdown', () {
test('shutdown', () async {
expect(server.running, isTrue);
// send request
var request = new ServerShutdownParams().toRequest('0');
var response = handler.handleRequest(request);
var response = await serverChannel.sendRequest(request);
expect(response, isResponseSuccess('0'));
// server is down
expect(server.running, isFalse);
});

View file

@ -694,6 +694,7 @@ class Server {
//
// Add server arguments.
//
arguments.add('--suppress-analytics');
if (diagnosticPort != null) {
arguments.add('--port');
arguments.add(diagnosticPort.toString());
@ -707,11 +708,9 @@ class Server {
if (useAnalysisHighlight2) {
arguments.add('--useAnalysisHighlight2');
}
// print('Launching $serverPath');
// print('$dartBinary ${arguments.join(' ')}');
// TODO(devoncarew): We could experiment with instead launching the analysis
// server in a separate isolate. This would make it easier to debug the
// integration tests, and would likely speed the tests up as well.
// integration tests, and would likely speed up the tests as well.
_process = await Process.start(dartBinary, arguments);
_process.exitCode.then((int code) {
if (code != 0) {

View file

@ -11,6 +11,7 @@ dart_package("analyzer_cli") {
deps = [
"//dart/pkg/analyzer",
"//dart/pkg/telemetry",
"//dart/third_party/pkg/linter",
"//third_party/dart-pkg/pub/args",
"//third_party/dart-pkg/pub/bazel_worker",

View file

@ -8,3 +8,4 @@ linter:
- empty_statements
- unnecessary_brace_in_string_interps
- valid_regexps

View file

@ -6,7 +6,9 @@
import 'package:analyzer_cli/starter.dart';
/// The entry point for the command-line analyzer.
void main(List<String> args) {
main(List<String> args) async {
CommandLineStarter starter = new CommandLineStarter();
starter.start(args);
// Wait for the starter to complete.
await starter.start(args);
}

View file

@ -76,7 +76,7 @@ class AnalyzerWorkerLoop extends SyncWorkerLoop {
}
// Prepare options.
CommandLineOptions options =
CommandLineOptions.parse(arguments, (String msg) {
CommandLineOptions.parse(arguments, printAndFail: (String msg) {
throw new ArgumentError(msg);
});
// Analyze and respond.

View file

@ -42,6 +42,7 @@ import 'package:analyzer_cli/starter.dart' show CommandLineStarter;
import 'package:front_end/src/base/performace_logger.dart';
import 'package:front_end/src/incremental/byte_store.dart';
import 'package:linter/src/rules.dart' as linter;
import 'package:meta/meta.dart';
import 'package:package_config/discovery.dart' as pkg_discovery;
import 'package:package_config/packages.dart' show Packages;
import 'package:package_config/packages_file.dart' as pkgfile show parse;
@ -49,16 +50,16 @@ import 'package:package_config/src/packages_impl.dart' show MapPackages;
import 'package:path/path.dart' as path;
import 'package:plugin/manager.dart';
import 'package:plugin/plugin.dart';
import 'package:telemetry/crash_reporting.dart';
import 'package:telemetry/telemetry.dart' as telemetry;
import 'package:yaml/yaml.dart';
/// Shared IO sink for standard error reporting.
///
/// *Visible for testing.*
@visibleForTesting
StringSink errorSink = io.stderr;
/// Shared IO sink for standard out reporting.
///
/// *Visible for testing.*
@visibleForTesting
StringSink outSink = io.stdout;
/// Test this option map to see if it specifies lint rules.
@ -67,6 +68,26 @@ bool containsLintRuleEntry(Map<String, YamlNode> options) {
return linterNode is YamlMap && linterNode.containsKey('rules');
}
telemetry.Analytics _analytics;
const _analyticsID = 'UA-26406144-28';
/// The analytics instance for analyzer-cli.
telemetry.Analytics get analytics => (_analytics ??=
telemetry.createAnalyticsInstance(_analyticsID, 'analyzer-cli'));
/// Make sure that we create an analytics instance that doesn't send for this
/// session.
void disableAnalyticsForSession() {
_analytics = telemetry.createAnalyticsInstance(_analyticsID, 'analyzer-cli',
disableForSession: true);
}
@visibleForTesting
void setAnalytics(telemetry.Analytics replacementAnalytics) {
_analytics = replacementAnalytics;
}
class Driver implements CommandLineStarter {
static final PerformanceTag _analyzeAllTag =
new PerformanceTag("Driver._analyzeAll");
@ -109,9 +130,24 @@ class Driver implements CommandLineStarter {
/// Collected analysis statistics.
final AnalysisStats stats = new AnalysisStats();
/// This Driver's current analysis context.
CrashReportSender _crashReportSender;
// TODO(devoncarew): Replace with the real crash product ID.
/// The crash reporting instance for analyzer-cli.
CrashReportSender get crashReportSender => (_crashReportSender ??=
new CrashReportSender('Dart_analyzer_cli', analytics));
/// Create a new Driver instance.
///
/// *Visible for testing.*
/// [isTesting] is true if we're running in a test environment.
Driver({bool isTesting: false}) {
if (isTesting) {
disableAnalyticsForSession();
}
}
/// This Driver's current analysis context.
@visibleForTesting
AnalysisContext get context => _context;
@override
@ -133,6 +169,15 @@ class Driver implements CommandLineStarter {
// Parse commandline options.
CommandLineOptions options = CommandLineOptions.parse(args);
if (options.batchMode || options.buildMode) {
disableAnalyticsForSession();
}
// Ping analytics with our initial call.
analytics.sendScreenView('home');
var timer = analytics.startTimer('analyze');
// Do analysis.
if (options.buildMode) {
ErrorSeverity severity = _buildModeAnalyze(options);
@ -140,7 +185,7 @@ class Driver implements CommandLineStarter {
if (_shouldBeFatal(severity, options)) {
io.exitCode = severity.ordinal;
}
} else if (options.shouldBatch) {
} else if (options.batchMode) {
BatchRunner batchRunner = new BatchRunner(outSink, errorSink);
batchRunner.runAsBatch(args, (List<String> args) async {
CommandLineOptions options = CommandLineOptions.parse(args);
@ -158,17 +203,30 @@ class Driver implements CommandLineStarter {
_analyzedFileCount += _context.sources.length;
}
// Send how long analysis took.
timer.finish();
// Send how many files were analyzed.
analytics.sendEvent('analyze', 'fileCount', value: _analyzedFileCount);
if (options.perfReport != null) {
String json = makePerfReport(
startTime, currentTimeMillis, options, _analyzedFileCount, stats);
new io.File(options.perfReport).writeAsStringSync(json);
}
// Wait a brief time for any analytics calls to finish.
await analytics.waitForLastPing(timeout: new Duration(milliseconds: 200));
analytics.close();
}
Future<ErrorSeverity> _analyzeAll(CommandLineOptions options) async {
PerformanceTag previous = _analyzeAllTag.makeCurrent();
try {
return await _analyzeAllImpl(options);
} catch (e, st) {
crashReportSender.sendReport(e, stackTrace: st);
rethrow;
} finally {
previous.makeCurrent();
}
@ -322,7 +380,7 @@ class Driver implements CommandLineStarter {
/// [AnalyzeFunctionBodiesPredicate] that implements this policy.
AnalyzeFunctionBodiesPredicate _chooseDietParsingPolicy(
CommandLineOptions options) {
if (options.shouldBatch) {
if (options.batchMode) {
// As analyzer is currently implemented, once a file has been diet
// parsed, it can't easily be un-diet parsed without creating a brand new
// context and losing caching. In batch mode, we can't predict which

View file

@ -11,6 +11,7 @@ import 'package:analyzer/src/util/sdk.dart';
import 'package:analyzer_cli/src/ansi.dart' as ansi;
import 'package:analyzer_cli/src/driver.dart';
import 'package:args/args.dart';
import 'package:telemetry/telemetry.dart' as telemetry;
const _binaryName = 'dartanalyzer';
@ -115,7 +116,7 @@ class CommandLineOptions {
final String perfReport;
/// Batch mode (for unit testing)
final bool shouldBatch;
final bool batchMode;
/// Whether to show package: warnings
final bool showPackageWarnings;
@ -184,7 +185,7 @@ class CommandLineOptions {
log = args['log'],
machineFormat = args['format'] == 'machine',
perfReport = args['x-perf-report'],
shouldBatch = args['batch'],
batchMode = args['batch'],
showPackageWarnings = args['show-package-warnings'] ||
args['package-warnings'] ||
args['x-package-warnings-prefix'] != null,
@ -238,8 +239,14 @@ class CommandLineOptions {
/// analyzer options. In case of a format error, calls [printAndFail], which
/// by default prints an error message to stderr and exits.
static CommandLineOptions parse(List<String> args,
[printAndFail(String msg) = printAndFail]) {
{printAndFail(String msg) = printAndFail}) {
CommandLineOptions options = _parse(args);
/// Only happens in testing.
if (options == null) {
return null;
}
// Check SDK.
if (!options.buildModePersistentWorker) {
// Infer if unspecified.
@ -346,6 +353,8 @@ class CommandLineOptions {
help: 'Treat non-type warnings as fatal.',
defaultsTo: false,
negatable: false)
..addFlag('analytics',
help: 'Enable or disable sending analytics information to Google.')
..addFlag('help',
abbr: 'h',
help:
@ -528,15 +537,25 @@ class CommandLineOptions {
// Help requests.
if (results['help']) {
_showUsage(parser);
_showUsage(parser, analytics, fromHelp: true);
exitHandler(0);
return null; // Only reachable in testing.
}
// Enable / disable analytics.
if (results.wasParsed('analytics')) {
analytics.enabled = results['analytics'];
outSink
.writeln(telemetry.createAnalyticsStatusMessage(analytics.enabled));
exitHandler(0);
return null; // Only reachable in testing.
}
// Batch mode and input files.
if (results['batch']) {
if (results.rest.isNotEmpty) {
errorSink.writeln('No source files expected in the batch mode.');
_showUsage(parser);
_showUsage(parser, analytics);
exitHandler(15);
return null; // Only reachable in testing.
}
@ -544,7 +563,7 @@ class CommandLineOptions {
if (results.rest.isNotEmpty) {
errorSink.writeln(
'No source files expected in the persistent worker mode.');
_showUsage(parser);
_showUsage(parser, analytics);
exitHandler(15);
return null; // Only reachable in testing.
}
@ -554,7 +573,7 @@ class CommandLineOptions {
return null; // Only reachable in testing.
} else {
if (results.rest.isEmpty && !results['build-mode']) {
_showUsage(parser);
_showUsage(parser, analytics, fromHelp: true);
exitHandler(15);
return null; // Only reachable in testing.
}
@ -562,21 +581,46 @@ class CommandLineOptions {
return new CommandLineOptions._fromArgs(results);
} on FormatException catch (e) {
errorSink.writeln(e.message);
_showUsage(parser);
_showUsage(parser, null);
exitHandler(15);
return null; // Only reachable in testing.
}
}
static _showUsage(ArgParser parser) {
static _showUsage(ArgParser parser, telemetry.Analytics analytics,
{bool fromHelp: false}) {
void printAnalyticsInfo() {
if (fromHelp) {
errorSink.writeln('');
errorSink.writeln(telemetry.analyticsNotice);
}
if (analytics != null) {
errorSink.writeln('');
errorSink.writeln(telemetry.createAnalyticsStatusMessage(
analytics.enabled,
command: 'analytics'));
}
}
errorSink.writeln(
'Usage: $_binaryName [options...] <directory or list of files>');
// If it's our first run, we display the analytics info more prominently.
if (analytics != null && analytics.firstRun) {
printAnalyticsInfo();
}
errorSink.writeln('');
errorSink.writeln(parser.usage);
if (analytics != null && !analytics.firstRun) {
printAnalyticsInfo();
}
errorSink.writeln('');
errorSink.writeln('''
Run "dartanalyzer -h -v" for verbose help output, including less commonly used options.
For more information, see http://www.dartlang.org/tools/analyzer.
''');
For more information, see https://www.dartlang.org/tools/analyzer.\n''');
}
}

View file

@ -4,6 +4,8 @@
library analyzer_cli.starter;
import 'dart:async';
import 'package:analyzer/plugin/resolver_provider.dart';
import 'package:analyzer_cli/src/driver.dart';
import 'package:plugin/plugin.dart';
@ -36,5 +38,5 @@ abstract class CommandLineStarter {
/**
* Use the given command-line [arguments] to start this analyzer.
*/
void start(List<String> arguments);
Future<Null> start(List<String> arguments);
}

View file

@ -14,8 +14,10 @@ dependencies:
package_config: '>=0.1.5 <2.0.0'
plugin: '>=0.1.0 <0.3.0'
protobuf: ^0.5.0
telemetry: ^0.0.1
yaml: ^2.1.2
dev_dependencies:
test_reflective_loader: ^0.1.0
typed_mock: '>=0.0.4 <1.0.0'
test: ^0.12.0
usage: ^3.2.0+1

View file

@ -72,7 +72,7 @@ class _Runner {
String get stdout => _stdout.toString();
Future<Null> run2(List<String> args) async {
await new Driver().start(args);
await new Driver(isTesting: true).start(args);
if (stderr.isNotEmpty) {
fail("Unexpected output to stderr:\n$stderr");
}

View file

@ -111,7 +111,7 @@ main() {
});
test('non-dangling part file', () async {
Driver driver = new Driver();
Driver driver = new Driver(isTesting: true);
await driver.start([
path.join(testDirectory, 'data/library_and_parts/lib.dart'),
path.join(testDirectory, 'data/library_and_parts/part1.dart')
@ -120,7 +120,7 @@ main() {
});
test('extra part file', () async {
Driver driver = new Driver();
Driver driver = new Driver(isTesting: true);
await driver.start([
path.join(testDirectory, 'data/library_and_parts/lib.dart'),
path.join(testDirectory, 'data/library_and_parts/part1.dart'),
@ -140,7 +140,7 @@ main() {
Directory origWorkingDir = Directory.current;
try {
Directory.current = path.join(tempDirPath, 'proj');
Driver driver = new Driver();
Driver driver = new Driver(isTesting: true);
try {
await driver.start([
path.join('lib', 'file.dart'),
@ -551,7 +551,7 @@ String adjustFileSpec(String fileSpec) {
Future<Null> drive(String source,
{String options: emptyOptionsFile,
List<String> args: const <String>[]}) async {
driver = new Driver();
driver = new Driver(isTesting: true);
var cmd = [
'--options',
path.join(testDirectory, options),

View file

@ -33,7 +33,7 @@ main() {
test('resolution', wrap(() async {
var testDir = path.join(testDirectory, 'data', 'embedder_client');
await new Driver().start([
await new Driver(isTesting: true).start([
'--packages',
path.join(testDir, '_packages'),
path.join(testDir, 'embedder_yaml_user.dart')
@ -45,7 +45,7 @@ main() {
test('sdk setup', wrap(() async {
var testDir = path.join(testDirectory, 'data', 'embedder_client');
Driver driver = new Driver();
Driver driver = new Driver(isTesting: true);
await driver.start([
'--packages',
path.join(testDir, '_packages'),

View file

@ -41,7 +41,7 @@ class ErrorsReportedOnceTest {
test_once() async {
String testDir = path.join(testDirectory, 'data', 'errors_reported_once');
Driver driver = new Driver();
Driver driver = new Driver(isTesting: true);
await driver.start(
[path.join(testDir, 'foo.dart'), path.join(testDir, 'bar.dart')]);

View file

@ -42,7 +42,7 @@ class ErrorUpgradeFailsCli {
test_once() async {
String testDir =
path.join(testDirectory, 'data', 'error_upgrade_fails_cli');
Driver driver = new Driver();
Driver driver = new Driver(isTesting: true);
await driver.start([path.join(testDir, 'foo.dart')]);
expect(exitCode, 3);

View file

@ -9,11 +9,39 @@ import 'dart:io';
import 'package:analyzer_cli/src/driver.dart';
import 'package:analyzer_cli/src/options.dart';
import 'package:test/test.dart';
import 'package:usage/usage.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
group('CommandLineOptions', () {
group('parse', () {
int lastExitHandlerCode;
StringBuffer outStringBuffer = new StringBuffer();
StringBuffer errorStringBuffer = new StringBuffer();
StringSink savedOutSink, savedErrorSink;
int savedExitCode;
ExitHandler savedExitHandler;
setUp(() {
savedOutSink = outSink;
savedErrorSink = errorSink;
savedExitHandler = exitHandler;
savedExitCode = exitCode;
exitHandler = (int code) {
lastExitHandlerCode = code;
};
outSink = outStringBuffer;
errorSink = errorStringBuffer;
});
tearDown(() {
outSink = savedOutSink;
errorSink = savedErrorSink;
exitCode = savedExitCode;
exitHandler = savedExitHandler;
});
test('defaults', () {
CommandLineOptions options =
CommandLineOptions.parse(['--dart-sdk', '.', 'foo.dart']);
@ -41,7 +69,7 @@ main() {
expect(options.log, isFalse);
expect(options.machineFormat, isFalse);
expect(options.packageRootPath, isNull);
expect(options.shouldBatch, isFalse);
expect(options.batchMode, isFalse);
expect(options.showPackageWarnings, isFalse);
expect(options.showSdkWarnings, isFalse);
expect(options.sourceFiles, equals(['foo.dart']));
@ -53,7 +81,7 @@ main() {
test('batch', () {
CommandLineOptions options =
CommandLineOptions.parse(['--dart-sdk', '.', '--batch']);
expect(options.shouldBatch, isTrue);
expect(options.batchMode, isTrue);
});
test('defined variables', () {
@ -195,17 +223,35 @@ main() {
var failureMessage;
CommandLineOptions.parse(
['--package-root', '.', '--packages', '.', 'foo.dart'],
(msg) => failureMessage = msg);
printAndFail: (msg) => failureMessage = msg);
expect(failureMessage,
equals("Cannot specify both '--package-root' and '--packages."));
});
test("bad SDK dir", () {
var failureMessage;
CommandLineOptions.parse(
['--dart-sdk', '&&&&&', 'foo.dart'], (msg) => failureMessage = msg);
CommandLineOptions.parse(['--dart-sdk', '&&&&&', 'foo.dart'],
printAndFail: (msg) => failureMessage = msg);
expect(failureMessage, equals('Invalid Dart SDK path: &&&&&'));
});
test('--analytics', () {
AnalyticsMock mock = new AnalyticsMock()..enabled = false;
setAnalytics(mock);
CommandLineOptions.parse(['--analytics']);
expect(mock.enabled, true);
expect(lastExitHandlerCode, 0);
expect(outStringBuffer.toString(), contains('Analytics are currently'));
});
test('--no-analytics', () {
AnalyticsMock mock = new AnalyticsMock()..enabled = false;
setAnalytics(mock);
CommandLineOptions.parse(['--no-analytics']);
expect(mock.enabled, false);
expect(lastExitHandlerCode, 0);
expect(outStringBuffer.toString(), contains('Analytics are currently'));
});
});
});
defineReflectiveTests(CommandLineOptionsTest);

View file

@ -61,7 +61,7 @@ class _Runner {
String get stdout => _stdout.toString();
Future<Null> run2(List<String> args) async {
await new Driver().start(args);
await new Driver(isTesting: true).start(args);
if (stderr.isNotEmpty) {
fail("Unexpected output to stderr:\n$stderr");
}

View file

@ -40,7 +40,7 @@ main() {
test('.packages file specified', () async {
String testDir = path.join(testDirectory, 'data', 'packages_file');
Driver driver = new Driver();
Driver driver = new Driver(isTesting: true);
await driver.start([
'--packages',
path.join(testDir, '_packages'),

View file

@ -43,7 +43,8 @@ not_main() {
test('produces stricter errors', () async {
var testPath = path.join(testDirectory, 'data/strong_example.dart');
new Driver().start(['--options', emptyOptionsFile, '--strong', testPath]);
new Driver(isTesting: true)
.start(['--options', emptyOptionsFile, '--strong', testPath]);
expect(exitCode, 3);
var stdout = outSink.toString();

View file

@ -43,7 +43,7 @@ void main() {
test('produces errors when option absent', () async {
var testPath = path.join(testDirectory, 'data/super_mixin_example.dart');
await new Driver().start([testPath]);
await new Driver(isTesting: true).start([testPath]);
expect(exitCode, 3);
var stdout = outSink.toString();
@ -61,7 +61,7 @@ void main() {
test('produces no errors when option present', () async {
var testPath = path.join(testDirectory, 'data/super_mixin_example.dart');
await new Driver().start(['--supermixin', testPath]);
await new Driver(isTesting: true).start(['--supermixin', testPath]);
expect(exitCode, 0);
var stdout = outSink.toString();

18
pkg/telemetry/BUILD.gn Normal file
View file

@ -0,0 +1,18 @@
# Copyright (c) 2017, 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("//build/dart/dart_package.gni")
dart_package("telemetry") {
package_name = "telemetry"
disable_analysis = true
deps = [
"//third_party/dart-pkg/pub/http",
"//third_party/dart-pkg/pub/path",
"//third_party/dart-pkg/pub/stack_trace",
"//third_party/dart-pkg/pub/usage",
]
}

View file

@ -32,12 +32,14 @@ class CrashReportSender {
static final Uri _baseUri = new Uri(
scheme: 'https', host: _crashServerHost, path: _crashEndpointPath);
final String crashProductId;
final Analytics analytics;
final http.Client _httpClient;
/// Create a new [CrashReportSender], using the data from the given
/// [Analytics] instance.
CrashReportSender(this.analytics, {http.Client httpClient})
CrashReportSender(this.crashProductId, this.analytics,
{http.Client httpClient})
: _httpClient = httpClient ?? new http.Client();
/// Sends one crash report.
@ -58,7 +60,7 @@ class CrashReportSender {
final http.MultipartRequest req = new http.MultipartRequest('POST', uri);
req.fields['uuid'] = analytics.clientId;
req.fields['product'] = analytics.trackingId;
req.fields['product'] = crashProductId;
req.fields['version'] = analytics.applicationVersion;
req.fields['osName'] = Platform.operatingSystem;
// TODO(devoncarew): Report the operating system version when we're able.

View file

@ -11,6 +11,11 @@ import 'package:usage/src/usage_impl_io.dart' as usage_io show getDartVersion;
import 'package:usage/usage.dart';
import 'package:usage/usage_io.dart';
export 'package:usage/usage.dart' show Analytics;
// TODO(devoncarew): Hard-coded to off for now. Remove when we're ready to ship.
final bool _HARD_CODE_OFF = true;
final String _dartDirectoryName = '.dart';
final String _settingsFileName = 'analytics.json';
@ -21,9 +26,24 @@ final String _settingsFileName = 'analytics.json';
///
/// `Analytics are currently enabled (and can be disabled with --no-analytics).`
final String analyticsNotice =
"Dart SDK tools anonymously report feature usage statistics and basic "
"crash reports to help improve Dart tools over time. See Google's privacy "
"policy: https://www.google.com/intl/en/policies/privacy/.";
"Dart SDK tools anonymously report feature usage statistics and basic crash\n"
"reports to help improve Dart tools over time. See Google's privacy policy:\n"
"https://www.google.com/intl/en/policies/privacy/.";
/// Return a customized message for command-line tools to display about the
/// state of analytics, and how users can enabled or disable analytics.
///
/// An example return value might be `'Analytics are currently enabled (and can
/// be disabled with --no-analytics).'`
String createAnalyticsStatusMessage(bool enabled,
{String command: 'analytics'}) {
String currentState = enabled ? 'enabled' : 'disabled';
String toggleState = enabled ? 'disabled' : 'enabled';
String commandToggle = enabled ? 'no-$command' : command;
return 'Analytics are currently $currentState '
'(and can be $toggleState with --$commandToggle).';
}
/// Create an [Analytics] instance with the given trackingID and
/// applicationName.
@ -37,6 +57,10 @@ Analytics createAnalyticsInstance(String trackingId, String applicationName,
dir.createSync();
}
if (_HARD_CODE_OFF) {
disableForSession = true;
}
File file = new File(path.join(dir.path, _settingsFileName));
return new _TelemetryAnalytics(
trackingId, applicationName, getDartVersion(), file, disableForSession);
@ -77,14 +101,14 @@ class _TelemetryAnalytics extends AnalyticsImpl {
@override
bool get enabled {
if (disableForSession || _isRunningOnBot()) {
if (disableForSession || isRunningOnBot()) {
return false;
}
return super.enabled;
}
}
bool _isRunningOnBot() {
bool isRunningOnBot() {
// - https://docs.travis-ci.com/user/environment-variables/
// - https://www.appveyor.com/docs/environment-variables/
// - CHROME_HEADLESS and BUILDBOT_BUILDERNAME are properties on Chrome infra

View file

@ -1,6 +1,6 @@
name: telemetry
description: A library to facilitate reporting analytics and crash reports.
version: 0.0.1-dev
version: 0.0.1
author: Dart Team <misc@dartlang.org>
environment:

View file

@ -24,9 +24,10 @@ void main() {
});
test('CrashReportSender', () async {
AnalyticsMock analytics = new AnalyticsMock();
CrashReportSender sender =
new CrashReportSender(analytics, httpClient: mockClient);
AnalyticsMock analytics = new AnalyticsMock()..enabled = false;
CrashReportSender sender = new CrashReportSender(
analytics.trackingId, analytics,
httpClient: mockClient);
await sender.sendReport('test-error', stackTrace: StackTrace.current);