Adds library cycle data to the analytics.

The two pieces of information we're tracking are
- the number of libraries in each library cycle
- the number of lines of code in each library cycle

The data is reported in terms of percentage.

This doesn't give us any way to correlate the two values. Knowing that
there is a library cycle with M libraries and a cycle with N lines of
code, doesn't tell us whether it's the same library cycle in both cases.
Still, I think it will help us understand the nature of the code that
we need to be able to analyze quickly.

Change-Id: I36a8bf11c4c6fdced6c524225be7a633062351ac
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437126
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index a76d886..ef9314b 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -69,6 +69,7 @@
     show EvictingFileByteStore;
 import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
 import 'package:analyzer/src/dart/analysis/info_declaration_store.dart';
+import 'package:analyzer/src/dart/analysis/library_graph.dart';
 import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:analyzer/src/dart/analysis/results.dart';
 import 'package:analyzer/src/dart/analysis/session.dart';
@@ -943,6 +944,7 @@
     var transitiveFileLineCount = 0;
     var transitiveFilePaths = <String>{};
     var transitiveFileUniqueLineCount = 0;
+    var libraryCycles = <LibraryCycle>{};
     var driverMap = contextManager.driverMap;
     for (var entry in driverMap.entries) {
       var rootPath = entry.key.path;
@@ -952,11 +954,18 @@
         packagesFileMap[rootPath] = contextRoot.packagesFile;
       }
       var fileSystemState = driver.fsState;
-      for (var fileState in fileSystemState.knownFiles) {
+      // Capture the known files before the loop to prevent a concurrent
+      // modification exception. The reason for the exception is unknown.
+      var knownFiles = fileSystemState.knownFiles.toList();
+      for (var fileState in knownFiles) {
         var isImmediate = fileState.path.startsWith(rootPath);
         if (isImmediate) {
           immediateFileCount++;
           immediateFileLineCount += fileState.lineInfo.lineCount;
+          var libraryKind = fileState.kind.library;
+          if (libraryKind != null) {
+            libraryCycles.add(libraryKind.libraryCycle);
+          }
         } else {
           var lineCount = fileState.lineInfo.lineCount;
           transitiveFileCount++;
@@ -969,8 +978,19 @@
     }
     var transitiveFileUniqueCount = transitiveFilePaths.length;
 
-    var rootPaths = packagesFileMap.keys.toList();
-    rootPaths.sort((first, second) => first.length.compareTo(second.length));
+    var libraryCycleLibraryCounts = <int>[];
+    var libraryCycleLineCounts = <int>[];
+    for (var libraryCycle in libraryCycles) {
+      var libraries = libraryCycle.libraries;
+      var lineCount = 0;
+      for (var library in libraries) {
+        for (var file in library.files) {
+          lineCount += file.lineInfo.lineCount;
+        }
+      }
+      libraryCycleLibraryCounts.add(libraries.length);
+      libraryCycleLineCounts.add(lineCount);
+    }
 
     analyticsManager.analysisComplete(
       numberOfContexts: driverMap.length,
@@ -980,6 +1000,8 @@
       transitiveFileLineCount: transitiveFileLineCount,
       transitiveFileUniqueCount: transitiveFileUniqueCount,
       transitiveFileUniqueLineCount: transitiveFileUniqueLineCount,
+      libraryCycleLibraryCounts: libraryCycleLibraryCounts,
+      libraryCycleLineCounts: libraryCycleLineCounts,
     );
   }
 
diff --git a/pkg/analysis_server/lib/src/analytics/analytics_manager.dart b/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
index f6af973..5653e08 100644
--- a/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
+++ b/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
@@ -98,8 +98,9 @@
   }
 
   /// Record information about the number of files and the number of lines of
-  /// code in those files, for both immediate files, transitive files, and the
-  /// number of unique transitive files.
+  /// code in those files, for both immediate files, transitive files, the
+  /// number of unique transitive files, and the number and sizes of library
+  /// cycles.
   void analysisComplete({
     required int numberOfContexts,
     required int immediateFileCount,
@@ -108,6 +109,8 @@
     required int transitiveFileLineCount,
     required int transitiveFileUniqueCount,
     required int transitiveFileUniqueLineCount,
+    required List<int> libraryCycleLibraryCounts,
+    required List<int> libraryCycleLineCounts,
   }) {
     // This is currently keeping the first report of completed analysis, but we
     // might want to consider alternatives, such as keeping the "largest"
@@ -120,6 +123,8 @@
       transitiveFileLineCount: transitiveFileLineCount,
       transitiveFileUniqueCount: transitiveFileUniqueCount,
       transitiveFileUniqueLineCount: transitiveFileUniqueLineCount,
+      libraryCycleLibraryCounts: libraryCycleLibraryCounts,
+      libraryCycleLineCounts: libraryCycleLineCounts,
     );
   }
 
@@ -461,6 +466,12 @@
       li(
         'transitiveFileUniqueLineCount: ${json.encode(analysisData.transitiveFileUniqueLineCount)}',
       );
+      li(
+        'libraryCycleLibraryCounts: ${analysisData.libraryCycleLibraryCounts.toAnalyticsString()}',
+      );
+      li(
+        'libraryCycleLineCounts: ${analysisData.libraryCycleLineCounts.toAnalyticsString()}',
+      );
       buffer.writeln('</ul>');
     }
 
@@ -505,6 +516,10 @@
           transitiveFileUniqueCount: contextStructure.transitiveFileUniqueCount,
           transitiveFileUniqueLineCount:
               contextStructure.transitiveFileUniqueLineCount,
+          libraryCycleLibraryCounts:
+              contextStructure.libraryCycleLibraryCounts.toAnalyticsString(),
+          libraryCycleLineCounts:
+              contextStructure.libraryCycleLineCounts.toAnalyticsString(),
         ),
       );
     }
diff --git a/pkg/analysis_server/lib/src/analytics/context_structure.dart b/pkg/analysis_server/lib/src/analytics/context_structure.dart
index 9e415a6..a5899ef 100644
--- a/pkg/analysis_server/lib/src/analytics/context_structure.dart
+++ b/pkg/analysis_server/lib/src/analytics/context_structure.dart
@@ -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 'package:analysis_server/src/analytics/percentile_calculator.dart';
+
 /// Data about the structure of the contexts being analyzed.
 ///
 /// The descriptions of the fields below depend on the following terms.
@@ -45,6 +47,9 @@
   /// [transitiveFileUniqueCount].
   final int transitiveFileUniqueLineCount;
 
+  final PercentileCalculator libraryCycleLibraryCounts;
+  final PercentileCalculator libraryCycleLineCounts;
+
   /// Initialize a newly created data holder.
   ContextStructure({
     required this.numberOfContexts,
@@ -54,5 +59,12 @@
     required this.transitiveFileLineCount,
     required this.transitiveFileUniqueCount,
     required this.transitiveFileUniqueLineCount,
-  });
+    required List<int> libraryCycleLibraryCounts,
+    required List<int> libraryCycleLineCounts,
+  }) : libraryCycleLibraryCounts = PercentileCalculator.from(
+         libraryCycleLibraryCounts,
+       ),
+       libraryCycleLineCounts = PercentileCalculator.from(
+         libraryCycleLineCounts,
+       );
 }
diff --git a/pkg/analysis_server/lib/src/analytics/percentile_calculator.dart b/pkg/analysis_server/lib/src/analytics/percentile_calculator.dart
index 8480df7..dcce05b4 100644
--- a/pkg/analysis_server/lib/src/analytics/percentile_calculator.dart
+++ b/pkg/analysis_server/lib/src/analytics/percentile_calculator.dart
@@ -17,6 +17,14 @@
   /// Initialize a newly created percentile calculator.
   PercentileCalculator();
 
+  factory PercentileCalculator.from(List<int> values) {
+    var calculator = PercentileCalculator();
+    for (var value in values) {
+      calculator.addValue(value);
+    }
+    return calculator;
+  }
+
   /// The number of values recorded.
   int get valueCount => _valueCount;