Convert the AnalyticsManager to use the analytics package

Change-Id: Id1822668e0ed7e12e88e990fa02247616b2e54e6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/285400
Reviewed-by: Elias Yishak <eliasyishak@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
This commit is contained in:
Brian Wilkerson 2023-03-01 17:54:52 +00:00 committed by Commit Queue
parent 814eda8edc
commit 0b8986902e
10 changed files with 250 additions and 271 deletions

View file

@ -566,7 +566,7 @@ abstract class AnalysisServer {
});
@mustCallSuper
void shutdown() {
Future<void> shutdown() async {
// For now we record plugins only on shutdown. We might want to record them
// every time the set of plugins changes, in which case we'll need to listen
// to the `PluginManager.pluginsChanged` stream.
@ -578,7 +578,7 @@ abstract class AnalysisServer {
analyticsManager.createdAnalysisContexts(contextManager.analysisContexts);
pubPackageService.shutdown();
analyticsManager.shutdown();
await analyticsManager.shutdown();
}
/// Return the path to the location of the byte store on disk, or `null` if

View file

@ -17,7 +17,7 @@ import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/status/pages.dart';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:collection/collection.dart';
import 'package:telemetry/telemetry.dart';
import 'package:dash_analytics/dash_analytics.dart';
/// An interface for managing and reporting analytics.
///
@ -153,21 +153,15 @@ class AnalyticsManager {
}
/// The server is shutting down. Report any accumulated analytics data.
void shutdown() {
Future<void> shutdown() async {
final sessionData = _sessionData;
if (sessionData == null) {
return;
}
_sendSessionData(sessionData);
_sendServerResponseTimes();
_sendPluginResponseTimes();
_sendNotificationHandlingTimes();
_sendLintUsageCounts();
_sendSeverityAdjustments();
await _sendSessionData(sessionData);
await _sendPeriodicData();
analytics.waitForLastPing(timeout: Duration(milliseconds: 200)).then((_) {
analytics.close();
});
analytics.close();
}
/// Record data from the given [params].
@ -211,20 +205,17 @@ class AnalyticsManager {
/// Record that the server was started at the given [time], that it was passed
/// the given command-line [arguments], that it was started by the client with
/// the given [clientId] and [clientVersion], and that it was invoked from an
/// SDK with the given [sdkVersion].
/// the given [clientId] and [clientVersion].
void startUp(
{required DateTime time,
required List<String> arguments,
required String clientId,
required String? clientVersion,
required String sdkVersion}) {
required String? clientVersion}) {
_sessionData = SessionData(
startTime: time,
commandLineArguments: arguments.join(','),
clientId: clientId,
clientVersion: clientVersion ?? '',
sdkVersion: sdkVersion);
clientVersion: clientVersion ?? '');
}
/// Return an HTML representation of the data that has been recorded.
@ -265,7 +256,6 @@ class AnalyticsManager {
li('duration: ${duration.toString()}');
li('flags: ${sessionData.commandLineArguments}');
li('parameters: ${sessionData.initializeParams}');
li('sdkVersion: ${sessionData.sdkVersion}');
li('plugins: ${_pluginData.usageCountData}');
buffer.writeln('</ul>');
@ -361,18 +351,21 @@ class AnalyticsManager {
requestData.responseTimes.addValue(responseTime);
}
void _sendLintUsageCounts() {
/// Send information about the number of times each lint is enabled in an
/// analysis options file.
Future<void> _sendLintUsageCounts() async {
if (_lintUsageCounts.isNotEmpty) {
analytics.sendEvent('language_server', 'lintUsageCounts', parameters: {
await analytics
.sendEvent(eventName: DashEvent.lintUsageCounts, eventData: {
'usageCounts': json.encode(_lintUsageCounts),
});
}
}
/// Send information about the notifications handled by the server.
void _sendNotificationHandlingTimes() {
Future<void> _sendNotificationHandlingTimes() async {
for (var data in _completedNotifications.values) {
analytics.sendEvent('language_server', 'notification', parameters: {
await analytics.sendEvent(eventName: DashEvent.notification, eventData: {
'latency': data.latencyTimes.toAnalyticsString(),
'method': data.method,
'duration': data.handlingTimes.toAnalyticsString(),
@ -380,12 +373,23 @@ class AnalyticsManager {
}
}
/// Send the information that is sent periodically, which is everything other
/// than the session data.
Future<void> _sendPeriodicData() async {
await _sendServerResponseTimes();
await _sendPluginResponseTimes();
await _sendNotificationHandlingTimes();
await _sendLintUsageCounts();
await _sendSeverityAdjustments();
}
/// Send information about the response times of plugins.
void _sendPluginResponseTimes() {
Future<void> _sendPluginResponseTimes() async {
var responseTimes = PluginManager.pluginResponseTimes;
for (var pluginEntry in responseTimes.entries) {
for (var responseEntry in pluginEntry.value.entries) {
analytics.sendEvent('language_server', 'pluginRequest', parameters: {
await analytics
.sendEvent(eventName: DashEvent.pluginRequest, eventData: {
'pluginId': pluginEntry.key.pluginId,
'method': responseEntry.key,
'duration': responseEntry.value.toAnalyticsString(),
@ -395,9 +399,9 @@ class AnalyticsManager {
}
/// Send information about the response times of server.
void _sendServerResponseTimes() {
Future<void> _sendServerResponseTimes() async {
for (var data in _completedRequests.values) {
analytics.sendEvent('language_server', 'request', parameters: {
await analytics.sendEvent(eventName: DashEvent.request, eventData: {
'latency': data.latencyTimes.toAnalyticsString(),
'method': data.method,
'duration': data.responseTimes.toAnalyticsString(),
@ -410,24 +414,25 @@ class AnalyticsManager {
}
/// Send information about the session.
void _sendSessionData(SessionData sessionData) {
Future<void> _sendSessionData(SessionData sessionData) async {
var endTime = DateTime.now().millisecondsSinceEpoch;
var duration = endTime - sessionData.startTime.millisecondsSinceEpoch;
analytics.sendEvent('language_server', 'session', parameters: {
await analytics.sendEvent(eventName: DashEvent.session, eventData: {
'flags': sessionData.commandLineArguments,
'parameters': sessionData.initializeParams,
'clientId': sessionData.clientId,
'clientVersion': sessionData.clientVersion,
'sdkVersion': sessionData.sdkVersion,
'duration': duration.toString(),
'plugins': _pluginData.usageCountData,
});
}
void _sendSeverityAdjustments() {
/// Send information about the number of times that the severity of a
/// diagnostic is changed in an analysis options file.
Future<void> _sendSeverityAdjustments() async {
if (_severityAdjustments.isNotEmpty) {
analytics
.sendEvent('language_server', 'severityAdjustments', parameters: {
await analytics
.sendEvent(eventName: DashEvent.severityAdjustments, eventData: {
'adjustmentCounts': json.encode(_severityAdjustments),
});
}

View file

@ -2,29 +2,30 @@
// 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 'package:telemetry/telemetry.dart';
import 'package:dash_analytics/dash_analytics.dart';
import 'package:dash_analytics/src/config_handler.dart';
import 'package:http/src/response.dart';
/// An implementation of [Analytics] that's appropriate to use when analytics
/// have not been enabled.
class NoopAnalytics extends Analytics {
/// An implementation of [Analytics] that's appropriate to use when running
/// tests.
// TODO(brianwilkerson) Remove this class when it's easier to create a test
// version of the `Analytics` class.
class NoopAnalytics implements Analytics {
@override
String? get applicationName => null;
Map<String, ToolInfo> get parsedTools => throw UnimplementedError();
@override
String? get applicationVersion => null;
bool get shouldShowMessage => false;
@override
bool get enabled => false;
bool get telemetryEnabled => false;
@override
set enabled(bool value) {
// Ignored
}
String get toolsMessage => throw UnimplementedError();
@override
Stream<Map<String, dynamic>> get onSend async* {
// Ignored
}
Map<String, Map<String, Object?>> get userPropertyMap =>
throw UnimplementedError();
@override
void close() {
@ -32,50 +33,20 @@ class NoopAnalytics extends Analytics {
}
@override
getSessionValue(String param) {
// Ignored
}
@override
dynamic noSuchMethod(Invocation invocation) {
LogFileStats? logFileStats() {
throw UnimplementedError();
}
@override
Future sendEvent(String category, String action,
{String? label, int? value, Map<String, String>? parameters}) async {
Future<Response>? sendEvent(
{required DashEvent eventName,
Map<String, Object?> eventData = const {}}) {
// Ignored
return null;
}
@override
Future sendException(String description, {bool? fatal}) async {
// Ignored
}
@override
Future sendScreenView(String viewName,
{Map<String, String>? parameters}) async {
// Ignored
}
@override
Future sendSocial(String network, String action, String target) async {
// Ignored
}
@override
Future sendTiming(String variableName, int time,
{String? category, String? label}) async {
// Ignored
}
@override
void setSessionValue(String param, value) {
// Ignored
}
@override
Future waitForLastPing({Duration? timeout}) async {
// Ignored
Future<void> setTelemetry(bool reportingBool) {
throw UnimplementedError();
}
}

View file

@ -20,14 +20,10 @@ class SessionData {
/// no version information was provided.
final String clientVersion;
/// The version of the SDK from which the server was started.
final String sdkVersion;
/// Initialize a newly created data holder.
SessionData(
{required this.startTime,
required this.commandLineArguments,
required this.clientId,
required this.clientVersion,
required this.sdkVersion});
required this.clientVersion});
}

View file

@ -695,8 +695,8 @@ class LegacyAnalysisServer extends AnalysisServer {
}
@override
Future<void> shutdown() {
super.shutdown();
Future<void> shutdown() async {
await super.shutdown();
pubApi.close();
@ -704,21 +704,21 @@ class LegacyAnalysisServer extends AnalysisServer {
// analyticsManager is being correctly initialized.
var analytics = options.analytics;
if (analytics != null) {
analytics.waitForLastPing(timeout: Duration(milliseconds: 200)).then((_) {
unawaited(analytics
.waitForLastPing(timeout: Duration(milliseconds: 200))
.then((_) {
analytics.close();
});
}));
}
detachableFileSystemManager?.dispose();
// Defer closing the channel and shutting down the instrumentation server so
// that the shutdown response can be sent and logged.
Future(() {
unawaited(Future(() {
instrumentationService.shutdown();
channel.close();
});
return Future.value();
}));
}
/// Implementation for `analysis.updateContent`.

View file

@ -368,6 +368,8 @@ class LspAnalysisServer extends AnalysisServer {
performanceAfterStartup = ServerPerformance();
performance = performanceAfterStartup!;
_checkAnalytics();
}
/// Handles a response from the client by invoking the completer that the
@ -683,7 +685,7 @@ class LspAnalysisServer extends AnalysisServer {
logErrorToClient(
'$message\n\n${error.message}\n\n${error.code}\n\n${error.data}');
shutdown();
unawaited(shutdown());
}
}
@ -844,19 +846,17 @@ class LspAnalysisServer extends AnalysisServer {
}
@override
Future<void> shutdown() {
super.shutdown();
Future<void> shutdown() async {
await super.shutdown();
detachableFileSystemManager?.dispose();
// Defer closing the channel so that the shutdown response can be sent and
// logged.
Future(() {
unawaited(Future(() {
channel.close();
});
_pluginChangeSubscription?.cancel();
return Future.value();
}));
unawaited(_pluginChangeSubscription?.cancel());
}
/// There was an error related to the socket from which messages are being
@ -896,6 +896,20 @@ class LspAnalysisServer extends AnalysisServer {
notifyFlutterWidgetDescriptions(path);
}
/// Display a message that will allow us to enable analytics on the next run.
void _checkAnalytics() {
var dashAnalytics = analyticsManager.analytics;
// The order of the conditions below is important. We need to check whether
// the client supports `showMessageRequest` before asking whether we need to
// show the message because the `Analytics` instance assumes that if
// `shouldShowMessage` returns `true` then the message will be shown.
if (_clientCapabilities!.supportsShowMessageRequest &&
dashAnalytics.shouldShowMessage) {
unawaited(
showUserPrompt(MessageType.Info, dashAnalytics.toolsMessage, ['Ok']));
}
}
/// Computes analysis roots for a set of open files.
///
/// This is used when there are no workspace folders open directly.

View file

@ -182,13 +182,13 @@ class Driver implements ServerStarter {
var sdkConfig = SdkConfiguration.readFromSdk();
analysisServerOptions.configurationOverrides = sdkConfig;
// Analytics
// Legacy Analytics
var disableAnalyticsForSession = results[SUPPRESS_ANALYTICS_FLAG] as bool;
if (results.wasParsed(TRAIN_USING)) {
disableAnalyticsForSession = true;
}
// Use sdkConfig to optionally override analytics settings.
// Use sdkConfig to optionally override legacy analytics settings.
final analyticsId = sdkConfig.analyticsId ?? 'UA-26406144-29';
final forceAnalyticsEnabled = sdkConfig.analyticsForceEnabled == true;
var analytics = telemetry.createAnalyticsInstance(
@ -204,7 +204,6 @@ class Driver implements ServerStarter {
if (analysisServerOptions.clientVersion != null) {
analytics.setSessionValue('cd1', analysisServerOptions.clientVersion);
}
var analyticsManager = AnalyticsManager(NoopAnalytics());
bool shouldSendCallback() {
// Check sdkConfig to optionally force reporting on.
@ -255,15 +254,26 @@ class Driver implements ServerStarter {
// can't be re-used, but the SDK is needed to create a package map provider
// in the case where we need to run `pub` in order to get the package map.
var defaultSdk = _createDefaultSdk(defaultSdkPath);
//
// Create the analytics manager.
// TODO(brianwilkerson) Add support for finding the version of the Flutter
// SDK, or `null` if we're not in a Flutter SDK.
// TODO(brianwilkerson) Delete the line below and uncomment the 3 lines
// below it when we have (a) fixed the `dart analyze` and `dart fix` tests
// so that they don't send events and (b) implemented a way for users to
// disable analytics.
var analyticsManager = AnalyticsManager(NoopAnalytics());
// var analyticsManager = AnalyticsManager(Analytics(
// tool: DashTool.languageServer,
// dartVersion: defaultSdk.sdkVersion,
// flutterVersion: null));
// Record the start of the session.
//
analyticsManager.startUp(
time: sessionStartTime,
arguments: _getArgumentsForAnalytics(results),
clientId: clientId,
clientVersion: analysisServerOptions.clientVersion,
sdkVersion: defaultSdk.sdkVersion);
clientVersion: analysisServerOptions.clientVersion);
//
// Initialize the instrumentation service.
//

View file

@ -10,7 +10,6 @@ import 'package:analysis_server/protocol/protocol_constants.dart'
show PROTOCOL_VERSION;
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/analytics/noop_analytics.dart';
import 'package:analysis_server/src/legacy_analysis_server.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart'
show LspAnalysisServer;
@ -160,39 +159,44 @@ String writeOption(String name, dynamic value) {
return '$name: <code>$value</code><br> ';
}
class AnalyticsPage extends DiagnosticPageWithNav {
AnalyticsPage(DiagnosticsSite site)
: super(site, 'analytics', 'Analytics',
description: 'Analytics gathered by the analysis server.');
@override
String? get navDetail => null;
@override
Future generateContent(Map<String, String> params) async {
var manager = server.analyticsManager;
//
// Display the standard header.
//
if (manager.analytics is NoopAnalytics) {
p('Analytics reporting disabled. In order to enable it, run:');
p('&nbsp;&nbsp;<code>dart --enable-analytics</code>');
p('If analytics had been enabled, the information below would be '
'reported on shutdown.');
} else {
p('The Dart tool uses Google Analytics to report feature usage '
'statistics and to send basic crash reports. This data is used to '
'help improve the Dart platform and tools over time.');
p('To disable reporting of analytics, run:');
p('&nbsp;&nbsp;<code>dart --disable-analytics</code>');
p('The information below will be reported on shutdown.');
}
//
// Display the analytics data that has been gathered.
//
manager.toHtml(buf);
}
}
// TODO(brianwilkerson) This page is disabled for production until we determine
// whether we want to display some information to the user and if so what
// information we want to display.
//
// class AnalyticsPage extends DiagnosticPageWithNav {
// AnalyticsPage(DiagnosticsSite site)
// : super(site, 'analytics', 'Analytics',
// description: 'Analytics gathered by the analysis server.');
//
// @override
// String? get navDetail => null;
//
// @override
// Future generateContent(Map<String, String> params) async {
// var manager = server.analyticsManager;
// //
// // Display the standard header.
// //
// if (!manager.analytics.telemetryEnabled) {
// p('Analytics reporting disabled. In order to enable it, run:');
// p('&nbsp;&nbsp;<code>dart --enable-analytics</code>');
// p('If analytics had been enabled, the information below would have been '
// 'reported.');
// } else {
// p('The Dart tool uses Google Analytics to report feature usage '
// 'statistics and to send basic crash reports. This data is used to '
// 'help improve the Dart platform and tools over time.');
// p('To disable reporting of analytics, run:');
// p('&nbsp;&nbsp;<code>dart --disable-analytics</code>');
// p('The information below will be reported the next time analytics are '
// 'sent.');
// }
// //
// // Display the analytics data that has been gathered.
// //
// manager.toHtml(buf);
// }
// }
class AstPage extends DiagnosticPageWithNav {
String? _description;

View file

@ -15,6 +15,7 @@ dependencies:
convert: any
crypto: any
dart_style: any
dash_analytics: any
http: any
linter: any
meta: any

View file

@ -15,8 +15,10 @@ import 'package:analyzer/dart/analysis/context_root.dart' as analyzer;
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:dash_analytics/dash_analytics.dart';
import 'package:dash_analytics/src/config_handler.dart';
import 'package:http/src/response.dart' as http;
import 'package:linter/src/rules.dart';
import 'package:telemetry/telemetry.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@ -35,7 +37,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
String get testPackageRootPath => testPackageRoot.path;
void test_createAnalysisContexts_lints() {
Future<void> test_createAnalysisContexts_lints() async {
_createAnalysisOptionsFile(lints: [
'avoid_dynamic_calls',
'await_only_futures',
@ -44,17 +46,17 @@ class AnalyticsManagerTest with ResourceProviderMixin {
var collection = _createContexts();
_defaultStartup();
manager.createdAnalysisContexts(collection.contexts);
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.lintUsageCounts(parameters: {
_ExpectedEvent.lintUsageCounts(eventData: {
'usageCounts':
'{"avoid_dynamic_calls":1,"await_only_futures":1,"unawaited_futures":1}',
}),
]);
}
void test_createAnalysisContexts_severityAdjustments() {
Future<void> test_createAnalysisContexts_severityAdjustments() async {
_createAnalysisOptionsFile(errors: {
'avoid_dynamic_calls': 'error',
'await_only_futures': 'ignore',
@ -62,25 +64,25 @@ class AnalyticsManagerTest with ResourceProviderMixin {
var collection = _createContexts();
_defaultStartup();
manager.createdAnalysisContexts(collection.contexts);
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.severityAdjustments(parameters: {
_ExpectedEvent.severityAdjustments(eventData: {
'adjustmentCounts':
'{"AVOID_DYNAMIC_CALLS":{"ERROR":1},"AWAIT_ONLY_FUTURES":{"ignore":1}}',
}),
]);
}
void test_plugin_request() {
Future<void> test_plugin_request() async {
_defaultStartup();
PluginManager.pluginResponseTimes[_MockPluginInfo('a')] = {
'analysis.getNavigation': PercentileCalculator(),
};
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.pluginRequest(parameters: {
_ExpectedEvent.pluginRequest(eventData: {
'pluginId': 'a',
'method': 'analysis.getNavigation',
'duration': _IsPercentiles(),
@ -89,7 +91,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
PluginManager.pluginResponseTimes.clear();
}
void test_server_notification() {
Future<void> test_server_notification() async {
_defaultStartup();
manager.handledNotificationMessage(
notification: NotificationMessage(
@ -98,10 +100,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
method: Method.workspace_didCreateFiles),
startTime: _now(),
endTime: _now());
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.notification(parameters: {
_ExpectedEvent.notification(eventData: {
'latency': _IsPercentiles(),
'method': Method.workspace_didCreateFiles.toString(),
'duration': _IsPercentiles(),
@ -109,7 +111,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
void test_server_request_analysisDidChangeWorkspaceFolders() {
Future<void> test_server_request_analysisDidChangeWorkspaceFolders() async {
_defaultStartup();
var params = DidChangeWorkspaceFoldersParams(
event: WorkspaceFoldersChangeEvent(added: [], removed: []));
@ -123,10 +125,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
.changedWorkspaceFolders(added: ['a', 'b', 'c'], removed: ['d', 'e']);
manager.sentResponseMessage(
response: ResponseMessage(jsonrpc: '', id: Either2.t1(1)));
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(parameters: {
_ExpectedEvent.request(eventData: {
'latency': _IsPercentiles(),
'method': Method.workspace_didChangeWorkspaceFolders.toString(),
'duration': _IsPercentiles(),
@ -138,7 +140,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
void test_server_request_analysisSetAnalysisRoots() {
Future<void> test_server_request_analysisSetAnalysisRoots() async {
_defaultStartup();
var params = AnalysisSetAnalysisRootsParams(['a', 'b', 'c'], ['d', 'e']);
var request =
@ -146,10 +148,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.startedRequest(request: request, startTime: _now());
manager.startedSetAnalysisRoots(params);
manager.sentResponse(response: Response('1'));
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(parameters: {
_ExpectedEvent.request(eventData: {
'latency': _IsPercentiles(),
'method': ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS,
'duration': _IsPercentiles(),
@ -161,7 +163,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
void test_server_request_analysisSetPriorityFiles() {
Future<void> test_server_request_analysisSetPriorityFiles() async {
_defaultStartup();
var params = AnalysisSetPriorityFilesParams(['a']);
var request =
@ -169,10 +171,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.startedRequest(request: request, startTime: _now());
manager.startedSetPriorityFiles(params);
manager.sentResponse(response: Response('1'));
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(parameters: {
_ExpectedEvent.request(eventData: {
'latency': _IsPercentiles(),
'method': ANALYSIS_REQUEST_SET_PRIORITY_FILES,
'duration': _IsPercentiles(),
@ -182,7 +184,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
void test_server_request_editGetRefactoring() {
Future<void> test_server_request_editGetRefactoring() async {
_defaultStartup();
var params =
EditGetRefactoringParams(RefactoringKind.RENAME, '', 0, 0, true);
@ -190,10 +192,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.startedRequest(request: request, startTime: _now());
manager.startedGetRefactoring(params);
manager.sentResponse(response: Response('1'));
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(parameters: {
_ExpectedEvent.request(eventData: {
'latency': _IsPercentiles(),
'method': EDIT_REQUEST_GET_REFACTORING,
'duration': _IsPercentiles(),
@ -202,7 +204,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
void test_server_request_initialize() {
Future<void> test_server_request_initialize() async {
_defaultStartup();
var params = InitializeParams(
capabilities: ClientCapabilities(),
@ -212,16 +214,16 @@ class AnalyticsManagerTest with ResourceProviderMixin {
'onlyAnalyzeProjectsWithOpenFiles': true,
});
manager.initialize(params);
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(parameters: {
_ExpectedEvent.session(eventData: {
'parameters':
'closingLabels,onlyAnalyzeProjectsWithOpenFiles,suggestFromUnimportedLibraries',
}),
]);
}
void test_server_request_initialized() {
Future<void> test_server_request_initialized() async {
_defaultStartup();
var params = InitializedParams();
var request = RequestMessage(
@ -233,10 +235,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.initialized(openWorkspacePaths: ['a', 'b', 'c']);
manager.sentResponseMessage(
response: ResponseMessage(jsonrpc: '', id: Either2.t1(1)));
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(parameters: {
_ExpectedEvent.request(eventData: {
'latency': _IsPercentiles(),
'method': Method.initialized.toString(),
'duration': _IsPercentiles(),
@ -246,15 +248,15 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
void test_server_request_noAdditional() {
Future<void> test_server_request_noAdditional() async {
_defaultStartup();
manager.startedRequest(
request: Request('1', SERVER_REQUEST_SHUTDOWN), startTime: _now());
manager.sentResponse(response: Response('1'));
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(parameters: {
_ExpectedEvent.request(eventData: {
'latency': _IsPercentiles(),
'method': SERVER_REQUEST_SHUTDOWN,
'duration': _IsPercentiles(),
@ -262,7 +264,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
void test_server_request_workspaceExecuteCommand() {
Future<void> test_server_request_workspaceExecuteCommand() async {
_defaultStartup();
var params = ExecuteCommandParams(command: 'doIt');
var request = RequestMessage(
@ -275,10 +277,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
manager.executedCommand('doIt');
manager.sentResponseMessage(
response: ResponseMessage(jsonrpc: '', id: Either2.t1(1)));
manager.shutdown();
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.request(parameters: {
_ExpectedEvent.request(eventData: {
'latency': _IsPercentiles(),
'method': Method.workspace_executeCommand.toString(),
'duration': _IsPercentiles(),
@ -287,68 +289,62 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
void test_shutdownWithoutStartup() {
manager.shutdown();
Future<void> test_shutdownWithoutStartup() async {
await manager.shutdown();
analytics.assertNoEvents();
}
void test_startup_withoutVersion() {
Future<void> test_startup_withoutVersion() async {
var arguments = ['a', 'b'];
var clientId = 'clientId';
var sdkVersion = 'sdkVersion';
manager.startUp(
time: DateTime.now(),
arguments: arguments,
clientId: clientId,
clientVersion: null,
sdkVersion: sdkVersion);
manager.shutdown();
clientVersion: null);
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(parameters: {
_ExpectedEvent.session(eventData: {
'flags': arguments.join(','),
'clientId': clientId,
'clientVersion': '',
'sdkVersion': sdkVersion,
'duration': _IsStringEncodedPositiveInt(),
}),
]);
}
void test_startup_withPlugins() {
Future<void> test_startup_withPlugins() async {
_defaultStartup();
manager.changedPlugins(_MockPluginManager(plugins: [
_MockPluginInfo('a'),
_MockPluginInfo('b'),
]));
manager.shutdown();
await manager.shutdown();
var counts =
'{"count":1,"percentiles":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}';
analytics.assertEvents([
_ExpectedEvent.session(parameters: {
_ExpectedEvent.session(eventData: {
'plugins': '{"recordCount":1,"rootCounts":{"a":$counts,"b":$counts}}'
}),
]);
}
void test_startup_withVersion() {
Future<void> test_startup_withVersion() async {
var arguments = ['a', 'b'];
var clientId = 'clientId';
var clientVersion = 'clientVersion';
var sdkVersion = 'sdkVersion';
manager.startUp(
time: DateTime.now(),
arguments: arguments,
clientId: clientId,
clientVersion: clientVersion,
sdkVersion: sdkVersion);
manager.shutdown();
clientVersion: clientVersion);
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(parameters: {
_ExpectedEvent.session(eventData: {
'flags': arguments.join(','),
'clientId': clientId,
'clientVersion': clientVersion,
'': isNull,
'sdkVersion': sdkVersion,
'duration': _IsStringEncodedPositiveInt(),
}),
]);
@ -411,11 +407,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
void _defaultStartup() {
manager.startUp(
time: DateTime.now(),
arguments: [],
clientId: '',
clientVersion: null,
sdkVersion: '');
time: DateTime.now(), arguments: [], clientId: '', clientVersion: null);
}
DateTime _now() => DateTime.now();
@ -423,66 +415,47 @@ class AnalyticsManagerTest with ResourceProviderMixin {
/// A record of an event that was reported to analytics.
class _Event {
final String category;
final String action;
final String? label;
final int? value;
final Map<String, String>? parameters;
final DashEvent eventName;
final Map<String, Object?> eventData;
_Event(this.category, this.action, this.label, this.value, this.parameters);
_Event(this.eventName, this.eventData);
}
/// A record of an event that was reported to analytics.
class _ExpectedEvent {
final String category;
final String action;
final String? label;
final int? value;
final Map<String, Object>? parameters;
final DashEvent eventName;
final Map<String, Object?>? eventData;
_ExpectedEvent(this.category, this.action,
{this.label, // ignore: unused_element
this.value, // ignore: unused_element
this.parameters});
_ExpectedEvent(this.eventName, this.eventData);
_ExpectedEvent.lintUsageCounts({Map<String, Object>? parameters})
: this('language_server', 'lintUsageCounts', parameters: parameters);
_ExpectedEvent.lintUsageCounts({Map<String, Object?>? eventData})
: this(DashEvent.lintUsageCounts, eventData);
_ExpectedEvent.notification({Map<String, Object>? parameters})
: this('language_server', 'notification', parameters: parameters);
_ExpectedEvent.notification({Map<String, Object?>? eventData})
: this(DashEvent.notification, eventData);
_ExpectedEvent.pluginRequest({Map<String, Object>? parameters})
: this('language_server', 'pluginRequest', parameters: parameters);
_ExpectedEvent.pluginRequest({Map<String, Object?>? eventData})
: this(DashEvent.pluginRequest, eventData);
_ExpectedEvent.request({Map<String, Object>? parameters})
: this('language_server', 'request', parameters: parameters);
_ExpectedEvent.request({Map<String, Object?>? eventData})
: this(DashEvent.request, eventData);
_ExpectedEvent.session({Map<String, Object>? parameters})
: this('language_server', 'session', parameters: parameters);
_ExpectedEvent.session({Map<String, Object?>? eventData})
: this(DashEvent.session, eventData);
_ExpectedEvent.severityAdjustments({Map<String, Object>? parameters})
: this('language_server', 'severityAdjustments', parameters: parameters);
_ExpectedEvent.severityAdjustments({Map<String, Object?>? eventData})
: this(DashEvent.severityAdjustments, eventData);
/// Compare the expected event with the [actual] event, failing if the actual
/// doesn't match the expected.
void matches(_Event actual) {
expect(actual.category, category);
expect(actual.action, action);
if (label != null) {
expect(actual.label, label);
}
if (value != null) {
expect(actual.value, value);
}
final actualParameters = actual.parameters;
final expectedParameters = parameters;
if (expectedParameters != null) {
if (actualParameters == null) {
fail('Expected parameters but found none');
}
for (var expectedKey in expectedParameters.keys) {
var actualValue = actualParameters[expectedKey];
var expectedValue = expectedParameters[expectedKey];
expect(actual.eventName, eventName);
final actualData = actual.eventData;
final expectedData = eventData;
if (expectedData != null) {
for (var expectedKey in expectedData.keys) {
var actualValue = actualData[expectedKey];
var expectedValue = expectedData[expectedKey];
expect(actualValue, expectedValue, reason: 'For key $expectedKey');
}
}
@ -491,17 +464,11 @@ class _ExpectedEvent {
@override
String toString() {
var buffer = StringBuffer();
buffer.write('category: ');
buffer.writeln(category);
buffer.write('action: ');
buffer.writeln(action);
buffer.write('label: ');
buffer.writeln(label);
buffer.write('value: ');
buffer.writeln(value);
var parameterMap = parameters;
if (parameterMap != null) {
for (var entry in parameterMap.entries) {
buffer.write('eventData: ');
buffer.writeln(eventData);
var data = eventData;
if (data != null) {
for (var entry in data.entries) {
buffer.write('value: ');
buffer.writeln('${entry.key}: ${entry.value}');
}
@ -566,6 +533,15 @@ class _MockAnalytics implements Analytics {
_MockAnalytics();
@override
Map<String, ToolInfo> get parsedTools => throw UnimplementedError();
@override
bool get shouldShowMessage => false;
@override
bool get telemetryEnabled => false;
void assertEvents(List<_ExpectedEvent> expectedEvents) {
var expectedCount = expectedEvents.length;
expect(events, hasLength(expectedCount));
@ -580,21 +556,23 @@ class _MockAnalytics implements Analytics {
@override
void close() {
// ignored
// Ignored
}
@override
LogFileStats? logFileStats() {
throw UnimplementedError();
}
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
Future sendEvent(String category, String action,
{String? label, int? value, Map<String, String>? parameters}) async {
events.add(_Event(category, action, label, value, parameters));
}
@override
Future waitForLastPing({Duration? timeout}) async {
// ignored
Future<http.Response>? sendEvent(
{required DashEvent eventName,
Map<String, Object?> eventData = const {}}) {
events.add(_Event(eventName, eventData));
return null;
}
}