| Index: pkg/analysis_server/lib/src/domain_diagnostic.dart
|
| diff --git a/pkg/analysis_server/lib/src/domain_diagnostic.dart b/pkg/analysis_server/lib/src/domain_diagnostic.dart
|
| index e6c08c4e1664495f8cb0144531e957e7b4f353db..e6a2ac03f0c003be9c9141350c9fa72cff10850f 100644
|
| --- a/pkg/analysis_server/lib/src/domain_diagnostic.dart
|
| +++ b/pkg/analysis_server/lib/src/domain_diagnostic.dart
|
| @@ -4,6 +4,7 @@
|
|
|
| library src.domain_diagnostic;
|
|
|
| +import 'dart:async';
|
| import 'dart:collection';
|
| import 'dart:core' hide Resource;
|
|
|
| @@ -19,42 +20,10 @@ import 'package:analyzer/src/generated/utilities_collection.dart';
|
| import 'package:analyzer/src/task/driver.dart';
|
| import 'package:analyzer/task/model.dart';
|
|
|
| -/// Extract context data from the given [context].
|
| -ContextData extractData(AnalysisContext context) {
|
| - int explicitFiles = 0;
|
| - int implicitFiles = 0;
|
| - int workItems = 0;
|
| - Set<String> exceptions = new HashSet<String>();
|
| - if (context is AnalysisContextImpl) {
|
| - // Work Item count.
|
| - AnalysisDriver driver = context.driver;
|
| - List<WorkItem> items = driver.currentWorkOrder?.workItems;
|
| - workItems ??= items?.length;
|
| - var cache = context.analysisCache;
|
| - if (cache is AnalysisCache) {
|
| - Set<AnalysisTarget> countedTargets = new HashSet<AnalysisTarget>();
|
| - MapIterator<AnalysisTarget, CacheEntry> iterator = cache.iterator();
|
| - while (iterator.moveNext()) {
|
| - AnalysisTarget target = iterator.key;
|
| - if (countedTargets.add(target)) {
|
| - CacheEntry cacheEntry = iterator.value;
|
| - if (target is Source) {
|
| - if (cacheEntry.explicitlyAdded) {
|
| - explicitFiles++;
|
| - } else {
|
| - implicitFiles++;
|
| - }
|
| - }
|
| - // Caught exceptions.
|
| - if (cacheEntry.exception != null) {
|
| - exceptions.add(cacheEntry.exception.toString());
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| - return new ContextData(context.name, explicitFiles, implicitFiles, workItems,
|
| - exceptions.toList());
|
| +int _workItemCount(AnalysisContextImpl context) {
|
| + AnalysisDriver driver = context.driver;
|
| + List<WorkItem> items = driver.currentWorkOrder?.workItems;
|
| + return items?.length ?? 0;
|
| }
|
|
|
| /// Instances of the class [DiagnosticDomainHandler] implement a
|
| @@ -66,20 +35,65 @@ class DiagnosticDomainHandler implements RequestHandler {
|
| /// The analysis server that is using this handler to process requests.
|
| final AnalysisServer server;
|
|
|
| + /// The sampler tracking rolling work queue length averages.
|
| + Sampler sampler;
|
| +
|
| /// Initialize a newly created handler to handle requests for the given
|
| /// [server].
|
| DiagnosticDomainHandler(this.server);
|
|
|
| /// Answer the `diagnostic.diagnostics` request.
|
| Response computeDiagnostics(Request request) {
|
| + // Initialize sampler if needed.
|
| + if (sampler == null) {
|
| + sampler = new Sampler(server);
|
| + }
|
| +
|
| List<ContextData> infos = <ContextData>[];
|
| server.folderMap.forEach((Folder folder, AnalysisContext context) {
|
| - infos.add(extractData(context));
|
| + infos.add(extractData(folder, context));
|
| });
|
|
|
| return new DiagnosticGetDiagnosticsResult(infos).toResponse(request.id);
|
| }
|
|
|
| + /// Extract context data from the given [context].
|
| + ContextData extractData(Folder folder, AnalysisContext context) {
|
| + int explicitFiles = 0;
|
| + int implicitFiles = 0;
|
| + int workItems = 0;
|
| + String workItemAverage = '-1';
|
| + Set<String> exceptions = new HashSet<String>();
|
| + if (context is AnalysisContextImpl) {
|
| + workItems = _workItemCount(context);
|
| + workItemAverage = sampler.getAverage(folder)?.toString() ?? '-1';
|
| + var cache = context.analysisCache;
|
| + if (cache is AnalysisCache) {
|
| + Set<AnalysisTarget> countedTargets = new HashSet<AnalysisTarget>();
|
| + MapIterator<AnalysisTarget, CacheEntry> iterator = cache.iterator();
|
| + while (iterator.moveNext()) {
|
| + AnalysisTarget target = iterator.key;
|
| + if (countedTargets.add(target)) {
|
| + CacheEntry cacheEntry = iterator.value;
|
| + if (target is Source) {
|
| + if (cacheEntry.explicitlyAdded) {
|
| + explicitFiles++;
|
| + } else {
|
| + implicitFiles++;
|
| + }
|
| + }
|
| + // Caught exceptions.
|
| + if (cacheEntry.exception != null) {
|
| + exceptions.add(cacheEntry.exception.toString());
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return new ContextData(context.name, explicitFiles, implicitFiles,
|
| + workItems, workItemAverage, exceptions.toList());
|
| + }
|
| +
|
| @override
|
| Response handleRequest(Request request) {
|
| try {
|
| @@ -93,3 +107,105 @@ class DiagnosticDomainHandler implements RequestHandler {
|
| return null;
|
| }
|
| }
|
| +
|
| +/// Keeps track of a moving average of work item queue lengths mapped to
|
| +/// contexts.
|
| +///
|
| +/// Sampling terminates after [maxSampleCount], if no one expresses interest
|
| +/// by calling [resetTimerCountdown].
|
| +class Sampler {
|
| + /// Timer interval.
|
| + static const Duration duration = const Duration(seconds: 1);
|
| +
|
| + /// Maximum number of samples taken between calls to [reset].
|
| + static const int maxSampleCount = 30;
|
| +
|
| + /// Current sample count.
|
| + int sampleCount = 0;
|
| +
|
| + /// The shared timer.
|
| + Timer timer;
|
| +
|
| + /// Map of contexts (tracked as folders to avoid leaks) to averages.
|
| + /// TOOD(pq): consider adding GC to remove mappings for deleted folders
|
| + Map<Folder, _Average> averages = new HashMap<Folder, _Average>();
|
| +
|
| + final AnalysisServer server;
|
| + Sampler(this.server) {
|
| + start();
|
| + _sample();
|
| + }
|
| +
|
| + /// Get the average for the context associated with the given [folder].
|
| + int getAverage(Folder folder) {
|
| + resetTimerCountdown();
|
| + return averages[folder].value;
|
| + }
|
| +
|
| + /// Check if we're currently sampling.
|
| + bool isSampling() => timer?.isActive ?? false;
|
| +
|
| + /// Reset counter.
|
| + void resetTimerCountdown() {
|
| + sampleCount = 0;
|
| + }
|
| +
|
| + /// Start sampling.
|
| + void start() {
|
| + // No need to (re)start if already sampling.
|
| + if (isSampling()) {
|
| + return;
|
| + }
|
| + timer = new Timer.periodic(duration, (Timer timer) {
|
| + _sample();
|
| + if (sampleCount++ >= maxSampleCount) {
|
| + timer.cancel();
|
| + }
|
| + });
|
| + }
|
| +
|
| + /// Stop sampling.
|
| + void stop() {
|
| + timer.cancel();
|
| + }
|
| +
|
| + /// Take a sample.
|
| + void _sample() {
|
| + try {
|
| + server.folderMap.forEach((Folder folder, AnalysisContext context) {
|
| + if (context is AnalysisContextImpl) {
|
| + _Average average = averages[folder];
|
| + if (average == null) {
|
| + average = new _Average();
|
| + averages[folder] = average;
|
| + }
|
| + average.addSample(_workItemCount(context));
|
| + }
|
| + });
|
| + } on Exception {
|
| + stop();
|
| + }
|
| + }
|
| +}
|
| +
|
| +/// Simple rolling average sample counter.
|
| +class _Average {
|
| + num _val;
|
| +
|
| + final int sampleCount;
|
| + _Average([this.sampleCount = 20]);
|
| +
|
| + num get value => _val ?? 0;
|
| +
|
| + void addSample(num sample) {
|
| + if (_val == null) {
|
| + _val = sample;
|
| + } else {
|
| + _val =
|
| + _val * ((sampleCount - 1) / sampleCount) + sample * (1 / sampleCount);
|
| + }
|
| + }
|
| +
|
| + @override
|
| + String toString() => 'average: ${value}';
|
| +}
|
|
|