blob: ccf1a67026f337c3a9f3407c2a2689b6d9751f04 [file] [log] [blame] [edit]
// Copyright (c) 2022, 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:convert';
import 'package:analysis_server/src/analytics/percentile_calculator.dart';
import 'package:analysis_server/src/plugin/plugin_isolate.dart';
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:path/path.dart' as path;
/// Data about the plugins associated with the context roots.
class PluginData {
/// The number of times that plugin information has been recorded.
int recordCount = 0;
/// A table mapping the IDs of running "new" plugin isolates to various
/// percentile-based analytics for each plugin.
Map<String, PluginDataPerIsolate> counts = {};
/// A table mapping the IDs of running legacy plugins to the percentile-based
/// analytics regarding the number of context roots associated with each of
/// the plugins.
Map<String, PercentileCalculator> usageCounts = {};
String get usageCountData {
return json.encode({
'recordCount': recordCount,
'rootCounts': _encodeUsageCounts(),
});
}
/// Records data about the plugins that are currently running, via info from
/// [pluginManager].
Future<void> recordPlugins(PluginManager pluginManager) async {
recordCount++;
for (var isolate in pluginManager.legacyPluginIsolates) {
usageCounts
.putIfAbsent(isolate.safePluginId, () => PercentileCalculator())
.addValue(isolate.contextRoots.length);
}
// Record a data point for each context root with _no_ configured new
// plugins.
for (var contextRootPath in pluginManager.contextRootsWithNoPlugins) {
// Plugin analytics are keyed to safe plugin IDs, which are derived from
// the context root path.
var pluginEntrypointPath = pluginManager.pluginStateFolderPath(
contextRootPath,
);
var safePluginId = pluginEntrypointPath.asSafePluginId;
counts.putIfAbsent(
safePluginId,
() => PluginDataPerIsolate(pluginCount: 0),
);
}
for (var isolate in pluginManager.newPluginIsolates) {
var details = await isolate.requestDetails();
if (details == null) continue;
var pluginDataPerIsolate = counts.putIfAbsent(
isolate.safePluginId,
() => PluginDataPerIsolate(pluginCount: details.plugins.length),
);
for (var plugin in details.plugins) {
pluginDataPerIsolate.lintRuleCounts.addValue(plugin.lintRules.length);
pluginDataPerIsolate.warningRuleCounts.addValue(
plugin.warningRules.length,
);
pluginDataPerIsolate.fixCounts.addValue(plugin.fixes.length);
pluginDataPerIsolate.assistCounts.addValue(plugin.assists.length);
}
}
}
/// Returns an encoding of the [usageCounts].
Map<String, Object> _encodeUsageCounts() => {
for (var entry in usageCounts.entries) entry.key: entry.value.toJson(),
};
}
/// Plugin data (for "new" plugins) for a given plugin isolate.
///
/// Only one plugin isolate runs for a given analysis context.
class PluginDataPerIsolate {
/// The number of running plugins.
final int pluginCount;
/// The percentile-based analytics regarding the number of lint rules which
/// are registered in each plugin.
PercentileCalculator lintRuleCounts = PercentileCalculator();
/// The percentile-based analytics regarding the number of warning rules which
/// are registered in each plugin.
PercentileCalculator warningRuleCounts = PercentileCalculator();
/// The percentile-based analytics regarding the number of quick fixes which
/// are registered in each plugin.
PercentileCalculator fixCounts = PercentileCalculator();
/// The percentile-based analytics regarding the number of quick assists which
/// are registered in each plugin.
PercentileCalculator assistCounts = PercentileCalculator();
PluginDataPerIsolate({required this.pluginCount});
}
extension on String {
String get asSafePluginId {
var components = path.split(this);
if (components.contains('.pub-cache')) {
var index = components.lastIndexOf('analyzer_plugin');
if (index > 2 &&
components[index - 1] == 'tools' &&
components[index - 3] == 'pub.dev') {
return components[index - 2];
}
}
return 'unknown';
}
}
extension PluginIsolateExtension on PluginIsolate {
/// An ID for this plugin that doesn't contain any PII.
///
/// If the plugin is installed in the pub cache and hosted on `pub.dev`, then
/// the returned name will be the name and version of the containing package
/// as listed on `pub.dev`. If not, then it might be an internal name so we
/// default to 'unknown'.
String get safePluginId => pluginId.asSafePluginId;
}