Pull in a new version of unified_analytics and update server

Change-Id: I7f63b55fc6a74f2adfc97f00950ca0516bae68ca
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/310965
Reviewed-by: Elias Yishak <eliasyishak@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2023-06-27 13:35:08 +00:00 committed by Commit Queue
parent 758727dd12
commit 45572cf7dd
4 changed files with 190 additions and 122 deletions

2
DEPS
View file

@ -179,7 +179,7 @@ vars = {
"test_descriptor_rev": "be7ce0751d85abd1ef605d14d359f499e1ba8fe2",
"test_process_rev": "5ff212250f6cc5f850b26295e7c8123bd5238ea3",
"test_reflective_loader_rev": "40d61b16647cd61b02d806fea46362ef07e7c502",
"tools_rev": "8d6e8b82e3eef8b2f5e3ec9bdd3dd1a2ad3e13f5",
"tools_rev": "8db0aa1e8c418289b9a972529b0f47902c559550",
"typed_data_rev": "8d29573ab6fd26a272dc846255eda66a01abae01",
"usage_rev": "6ee09084596f9829371bb836f9d1ca05820f29ea",
"vector_math_rev": "c14703830d47818a82ce8a9fcb70029e5e8e6a16",

View file

@ -35,6 +35,22 @@ class AnalyticsManager {
/// development-time analytics account.
static const bool sendExperimentalData = false;
static const addedKey = 'added';
static const removedKey = 'removed';
static const commandEnumKey = 'command';
static const openWorkspacePathsKey = 'openWorkspacePaths';
static const refactoringKindEnumKey = EDIT_REQUEST_GET_REFACTORING_KIND;
static const includedKey = ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS_INCLUDED;
static const excludedKey = ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS_EXCLUDED;
static const filesKey = ANALYSIS_REQUEST_SET_PRIORITY_FILES_FILES;
/// The object used to send analytics.
final Analytics analytics;
@ -125,8 +141,8 @@ class AnalyticsManager {
{required List<String> added, required List<String> removed}) {
var requestData =
getRequestData(Method.workspace_didChangeWorkspaceFolders.toString());
requestData.addValue('added', added.length);
requestData.addValue('removed', removed.length);
requestData.addValue(addedKey, added.length);
requestData.addValue(removedKey, removed.length);
}
/// Record that the [contexts] have been created.
@ -149,7 +165,7 @@ class AnalyticsManager {
void executedCommand(String command) {
var requestData =
getRequestData(Method.workspace_executeCommand.toString());
requestData.addEnumValue('command', command);
requestData.addEnumValue(commandEnumKey, command);
}
/// Return the request data for requests that have the given [method].
@ -195,7 +211,7 @@ class AnalyticsManager {
/// Record the number of [openWorkspacePaths].
void initialized({required List<String> openWorkspacePaths}) {
var requestData = getRequestData(Method.initialized.toString());
requestData.addValue('openWorkspacePaths', openWorkspacePaths.length);
requestData.addValue(openWorkspacePathsKey, openWorkspacePaths.length);
}
Future<void> sendMemoryUsage(MemoryUsageEvent event) async {
@ -205,19 +221,19 @@ class AnalyticsManager {
assert((event.delta == null) == (event.period == null));
if (delta == null || seconds == null) {
await analytics.sendEvent(eventName: DashEvent.memoryInfo, eventData: {
'rss': event.rss,
});
await analytics.send(Event.memoryInfo(
rss: event.rss,
));
return;
}
if (seconds == 0) seconds = 1;
await analytics.sendEvent(eventName: DashEvent.memoryInfo, eventData: {
'rss': event.rss,
'periodSec': seconds,
'mbPerSec': delta / seconds,
});
await analytics.send(Event.memoryInfo(
rss: event.rss,
periodSec: seconds,
mbPerSec: delta / seconds,
));
}
/// Record that the given [response] was sent to the client.
@ -254,8 +270,7 @@ class AnalyticsManager {
/// Record data from the given [params].
void startedGetRefactoring(EditGetRefactoringParams params) {
var requestData = getRequestData(EDIT_REQUEST_GET_REFACTORING);
requestData.addEnumValue(
EDIT_REQUEST_GET_REFACTORING_KIND, params.kind.name);
requestData.addEnumValue(refactoringKindEnumKey, params.kind.name);
}
/// Record that the server started working on the give [request] at the given
@ -277,10 +292,8 @@ class AnalyticsManager {
/// Record data from the given [params].
void startedSetAnalysisRoots(AnalysisSetAnalysisRootsParams params) {
var requestData = getRequestData(ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS);
requestData.addValue(
ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS_INCLUDED, params.included.length);
requestData.addValue(
ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS_EXCLUDED, params.excluded.length);
requestData.addValue(includedKey, params.included.length);
requestData.addValue(excludedKey, params.excluded.length);
}
/// Record data from the given [params].
@ -456,21 +469,20 @@ class AnalyticsManager {
Future<void> _sendAnalysisData() async {
var contextStructure = _contextStructure;
if (contextStructure != null) {
await analytics
.sendEvent(eventName: DashEvent.contextStructure, eventData: {
'numberOfContexts': contextStructure.numberOfContexts,
'contextsWithoutFiles': contextStructure.contextsWithoutFiles,
'contextsFromPackagesFiles': contextStructure.contextsFromPackagesFiles,
'contextsFromOptionsFiles': contextStructure.contextsFromOptionsFiles,
'contextsFromBothFiles': contextStructure.contextsFromBothFiles,
'immediateFileCount': contextStructure.immediateFileCount,
'immediateFileLineCount': contextStructure.immediateFileLineCount,
'transitiveFileCount': contextStructure.transitiveFileCount,
'transitiveFileLineCount': contextStructure.transitiveFileLineCount,
'transitiveFileUniqueCount': contextStructure.transitiveFileUniqueCount,
'transitiveFileUniqueLineCount':
await analytics.send(Event.contextStructure(
numberOfContexts: contextStructure.numberOfContexts,
contextsWithoutFiles: contextStructure.contextsWithoutFiles,
contextsFromPackagesFiles: contextStructure.contextsFromPackagesFiles,
contextsFromOptionsFiles: contextStructure.contextsFromOptionsFiles,
contextsFromBothFiles: contextStructure.contextsFromBothFiles,
immediateFileCount: contextStructure.immediateFileCount,
immediateFileLineCount: contextStructure.immediateFileLineCount,
transitiveFileCount: contextStructure.transitiveFileCount,
transitiveFileLineCount: contextStructure.transitiveFileLineCount,
transitiveFileUniqueCount: contextStructure.transitiveFileUniqueCount,
transitiveFileUniqueLineCount:
contextStructure.transitiveFileUniqueLineCount,
});
));
}
}
@ -478,12 +490,14 @@ class AnalyticsManager {
/// analysis options file.
Future<void> _sendLintUsageCounts() async {
if (_lintUsageCounts.isNotEmpty) {
var lintUsageCounts = json.encode(_lintUsageCounts);
var entries = _lintUsageCounts.entries.toList();
_lintUsageCounts.clear();
await analytics
.sendEvent(eventName: DashEvent.lintUsageCounts, eventData: {
'usageCounts': lintUsageCounts,
});
for (var entry in entries) {
await analytics.send(Event.lintUsageCount(
count: entry.value,
name: entry.key,
));
}
}
}
@ -493,12 +507,11 @@ class AnalyticsManager {
var completedNotifications = _completedNotifications.values.toList();
_completedNotifications.clear();
for (var data in completedNotifications) {
await analytics
.sendEvent(eventName: DashEvent.clientNotification, eventData: {
'latency': data.latencyTimes.toAnalyticsString(),
'method': data.method,
'duration': data.handlingTimes.toAnalyticsString(),
});
await analytics.send(Event.clientNotification(
latency: data.latencyTimes.toAnalyticsString(),
method: data.method,
duration: data.handlingTimes.toAnalyticsString(),
));
}
}
}
@ -521,12 +534,11 @@ class AnalyticsManager {
responseTimes.clear();
for (var pluginEntry in entries) {
for (var responseEntry in pluginEntry.value.entries) {
await analytics
.sendEvent(eventName: DashEvent.pluginRequest, eventData: {
'pluginId': pluginEntry.key.safePluginId,
'method': responseEntry.key,
'duration': responseEntry.value.toAnalyticsString(),
});
await analytics.send(Event.pluginRequest(
pluginId: pluginEntry.key.safePluginId,
method: responseEntry.key,
duration: responseEntry.value.toAnalyticsString(),
));
}
}
}
@ -538,16 +550,33 @@ class AnalyticsManager {
var completedRequests = _completedRequests.values.toList();
_completedRequests.clear();
for (var data in completedRequests) {
await analytics
.sendEvent(eventName: DashEvent.clientRequest, eventData: {
'latency': data.latencyTimes.toAnalyticsString(),
'method': data.method,
'duration': data.responseTimes.toAnalyticsString(),
for (var field in data.additionalPercentiles.entries)
field.key: field.value.toAnalyticsString(),
for (var field in data.additionalEnumCounts.entries)
field.key: json.encode(field.value),
});
await analytics.send(Event.clientRequest(
latency: data.latencyTimes.toAnalyticsString(),
method: data.method,
duration: data.responseTimes.toAnalyticsString(),
added: data.additionalPercentiles[addedKey]?.toAnalyticsString(),
excluded:
data.additionalPercentiles[excludedKey]?.toAnalyticsString(),
files: data.additionalPercentiles[filesKey]?.toAnalyticsString(),
included:
data.additionalPercentiles[includedKey]?.toAnalyticsString(),
openWorkspacePaths: data.additionalPercentiles[openWorkspacePathsKey]
?.toAnalyticsString(),
removed: data.additionalPercentiles[removedKey]?.toAnalyticsString(),
));
var commandMap = data.additionalEnumCounts[commandEnumKey];
if (commandMap != null) {
for (var entry in commandMap.entries) {
await analytics.send(Event.commandExecuted(
count: entry.value,
name: entry.key,
));
}
}
// TODO(brianwilkerson) We don't appear to have an event defined that we
// can use to send analytics about how often old-style refactorings are
// being invoked.
// var refactoringMap = data.additionalEnumCounts[refactoringKindEnumKey];
}
}
}
@ -556,26 +585,33 @@ class AnalyticsManager {
Future<void> _sendSessionData(SessionData sessionData) async {
var endTime = DateTime.now().millisecondsSinceEpoch;
var duration = endTime - sessionData.startTime.millisecondsSinceEpoch;
await analytics.sendEvent(eventName: DashEvent.serverSession, eventData: {
'flags': sessionData.commandLineArguments,
'parameters': sessionData.initializeParams,
'clientId': sessionData.clientId,
'clientVersion': sessionData.clientVersion,
'duration': duration.toString(),
'plugins': _pluginData.usageCountData,
});
await analytics.send(Event.serverSession(
flags: sessionData.commandLineArguments,
parameters: sessionData.initializeParams,
clientId: sessionData.clientId,
clientVersion: sessionData.clientVersion,
duration: duration,
));
for (var entry in _pluginData.usageCounts.entries) {
await analytics.send(Event.pluginUse(
count: _pluginData.recordCount,
enabled: entry.value.toAnalyticsString(),
pluginId: entry.key));
}
}
/// 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) {
var severityAdjustments = json.encode(_severityAdjustments);
var entries = _severityAdjustments.entries.toList();
_severityAdjustments.clear();
await analytics
.sendEvent(eventName: DashEvent.severityAdjustments, eventData: {
'adjustmentCounts': severityAdjustments,
});
for (var entry in entries) {
await analytics.send(Event.severityAdjustment(
adjustments: json.encode(entry.value),
diagnostic: entry.key,
));
}
}
}
}

View file

@ -43,9 +43,7 @@ class NoopAnalytics implements Analytics {
}
@override
Future<Response>? sendEvent(
{required DashEvent eventName,
Map<String, Object?> eventData = const {}}) {
Future<Response>? send(Event event) {
// Ignored
return null;
}

View file

@ -21,6 +21,7 @@ import 'package:linter/src/rules.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package:unified_analytics/src/enums.dart';
import 'package:unified_analytics/unified_analytics.dart';
void main() {
@ -38,6 +39,9 @@ class AnalyticsManagerTest with ResourceProviderMixin {
String get testPackageRootPath => testPackageRoot.path;
DateTime get _startUpTime => DateTime.fromMillisecondsSinceEpoch(
DateTime.now().millisecondsSinceEpoch - 5);
Future<void> test_createAnalysisContexts_lints() async {
_createAnalysisOptionsFile(lints: [
'avoid_dynamic_calls',
@ -50,9 +54,17 @@ class AnalyticsManagerTest with ResourceProviderMixin {
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.lintUsageCounts(eventData: {
'usageCounts':
'{"avoid_dynamic_calls":1,"await_only_futures":1,"unawaited_futures":1}',
_ExpectedEvent.lintUsageCount(eventData: {
'count': 1,
'name': 'avoid_dynamic_calls',
}),
_ExpectedEvent.lintUsageCount(eventData: {
'count': 1,
'name': 'await_only_futures',
}),
_ExpectedEvent.lintUsageCount(eventData: {
'count': 1,
'name': 'unawaited_futures',
}),
]);
}
@ -68,9 +80,13 @@ class AnalyticsManagerTest with ResourceProviderMixin {
await manager.shutdown();
analytics.assertEvents([
_ExpectedEvent.session(),
_ExpectedEvent.severityAdjustments(eventData: {
'adjustmentCounts':
'{"AVOID_DYNAMIC_CALLS":{"ERROR":1},"AWAIT_ONLY_FUTURES":{"ignore":1}}',
_ExpectedEvent.severityAdjustment(eventData: {
'diagnostic': 'AVOID_DYNAMIC_CALLS',
'adjustments': '{"ERROR":1}',
}),
_ExpectedEvent.severityAdjustment(eventData: {
'diagnostic': 'AWAIT_ONLY_FUTURES',
'adjustments': '{"ignore":1}',
}),
]);
}
@ -183,6 +199,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
]);
}
@FailingTest(reason: 'We are currently unable to send refactoring events')
Future<void> test_server_request_editGetRefactoring() async {
_defaultStartup();
var params =
@ -282,7 +299,10 @@ class AnalyticsManagerTest with ResourceProviderMixin {
'latency': _IsPercentiles(),
'method': Method.workspace_executeCommand.toString(),
'duration': _IsPercentiles(),
'command': '{"doIt":2}'
}),
_ExpectedEvent.commandExecuted(eventData: {
'name': 'doIt',
'count': 2,
}),
]);
}
@ -296,7 +316,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
var arguments = ['a', 'b'];
var clientId = 'clientId';
manager.startUp(
time: DateTime.now(),
time: _startUpTime,
arguments: arguments,
clientId: clientId,
clientVersion: null);
@ -306,7 +326,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
'flags': arguments.join(','),
'clientId': clientId,
'clientVersion': '',
'duration': _IsStringEncodedPositiveInt(),
'duration': _IsPositiveInt(),
}),
]);
}
@ -318,10 +338,17 @@ class AnalyticsManagerTest with ResourceProviderMixin {
_pluginInfo('b'),
]));
await manager.shutdown();
var counts = '{"count":1,"percentiles":[0,0,0,0,0]}';
analytics.assertEvents([
_ExpectedEvent.session(eventData: {
'plugins': '{"recordCount":1,"rootCounts":{"a":$counts,"b":$counts}}'
_ExpectedEvent.session(eventData: {}),
_ExpectedEvent.pluginUse(eventData: {
'count': 1,
'pluginId': 'a',
'enabled': _IsPercentiles(),
}),
_ExpectedEvent.pluginUse(eventData: {
'count': 1,
'pluginId': 'b',
'enabled': _IsPercentiles(),
}),
]);
}
@ -331,7 +358,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
var clientId = 'clientId';
var clientVersion = 'clientVersion';
manager.startUp(
time: DateTime.now(),
time: _startUpTime,
arguments: arguments,
clientId: clientId,
clientVersion: clientVersion);
@ -341,8 +368,7 @@ class AnalyticsManagerTest with ResourceProviderMixin {
'flags': arguments.join(','),
'clientId': clientId,
'clientVersion': clientVersion,
'': isNull,
'duration': _IsStringEncodedPositiveInt(),
'duration': _IsPositiveInt(),
}),
]);
}
@ -413,14 +439,6 @@ class AnalyticsManagerTest with ResourceProviderMixin {
path.join('.pub-cache', 'pub.dev', name, 'tools', 'analyzer_plugin'));
}
/// A record of an event that was reported to analytics.
class _Event {
final DashEvent eventName;
final Map<String, Object?> eventData;
_Event(this.eventName, this.eventData);
}
/// A record of an event that was reported to analytics.
class _ExpectedEvent {
final DashEvent eventName;
@ -428,8 +446,11 @@ class _ExpectedEvent {
_ExpectedEvent(this.eventName, this.eventData);
_ExpectedEvent.lintUsageCounts({Map<String, Object?>? eventData})
: this(DashEvent.lintUsageCounts, eventData);
_ExpectedEvent.commandExecuted({Map<String, Object?>? eventData})
: this(DashEvent.commandExecuted, eventData);
_ExpectedEvent.lintUsageCount({Map<String, Object?>? eventData})
: this(DashEvent.lintUsageCount, eventData);
_ExpectedEvent.notification({Map<String, Object?>? eventData})
: this(DashEvent.clientNotification, eventData);
@ -437,18 +458,21 @@ class _ExpectedEvent {
_ExpectedEvent.pluginRequest({Map<String, Object?>? eventData})
: this(DashEvent.pluginRequest, eventData);
_ExpectedEvent.pluginUse({Map<String, Object?>? eventData})
: this(DashEvent.pluginUse, eventData);
_ExpectedEvent.request({Map<String, Object?>? eventData})
: this(DashEvent.clientRequest, eventData);
_ExpectedEvent.session({Map<String, Object?>? eventData})
: this(DashEvent.serverSession, eventData);
_ExpectedEvent.severityAdjustments({Map<String, Object?>? eventData})
: this(DashEvent.severityAdjustments, eventData);
_ExpectedEvent.severityAdjustment({Map<String, Object?>? eventData})
: this(DashEvent.severityAdjustment, eventData);
/// Compare the expected event with the [actual] event, failing if the actual
/// doesn't match the expected.
void matches(_Event actual) {
void matches(Event actual) {
expect(actual.eventName, eventName);
final actualData = actual.eventData;
final expectedData = eventData;
@ -456,6 +480,17 @@ class _ExpectedEvent {
for (var expectedKey in expectedData.keys) {
var actualValue = actualData[expectedKey];
var expectedValue = expectedData[expectedKey];
if (!(actualValue == expectedValue ||
(expectedValue is Matcher &&
expectedValue.matches(actualValue, {})))) {
var buffer = StringBuffer();
buffer.writeln('Incorrect event data.');
buffer.writeln('Expected:');
writeMap(buffer, expectedData);
buffer.writeln('Actual:');
writeMap(buffer, actualData);
fail(buffer.toString());
}
expect(actualValue, expectedValue, reason: 'For key $expectedKey');
}
}
@ -475,6 +510,15 @@ class _ExpectedEvent {
}
return buffer.toString();
}
void writeMap(StringBuffer buffer, Map<String, Object?> map) {
for (var entry in map.entries) {
buffer.write(' ');
buffer.write(entry.key);
buffer.write(': ');
buffer.writeln(entry.value);
}
}
}
/// A matcher for strings containing positive integer values.
@ -506,30 +550,22 @@ class _IsPercentiles extends Matcher {
}
/// A matcher for strings containing positive integer values.
class _IsStringEncodedPositiveInt extends Matcher {
const _IsStringEncodedPositiveInt();
class _IsPositiveInt extends Matcher {
const _IsPositiveInt();
@override
Description describe(Description description) =>
description.add('a string encoded positive integer');
description.add('a positive integer');
@override
bool matches(Object? item, Map<Object?, Object?> matchState) {
if (item is! String) {
return false;
}
try {
var value = int.parse(item);
return value >= 0;
} catch (exception) {
return false;
}
return item is int && item >= 0;
}
}
/// An implementation of [Analytics] specialized for testing.
class _MockAnalytics implements NoopAnalytics {
List<_Event> events = [];
List<Event> events = [];
_MockAnalytics();
@ -568,11 +604,9 @@ class _MockAnalytics implements NoopAnalytics {
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
Future<http.Response>? sendEvent(
{required DashEvent eventName,
Map<String, Object?> eventData = const {}}) {
events.add(_Event(eventName, eventData));
return null;
Future<http.Response>? send(Event event) async {
events.add(event);
return http.Response('', 200);
}
}