| // Copyright (c) 2025, 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:async'; |
| |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/file_system/memory_file_system.dart'; |
| import 'package:analyzer/file_system/physical_file_system.dart'; |
| import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/dart/analysis/library_graph.dart'; |
| import 'package:analyzer/src/dart/analysis/performance_logger.dart'; |
| import 'package:analyzer/src/test_utilities/mock_sdk.dart'; |
| import 'package:analyzer/src/util/file_paths.dart' as file_paths; |
| |
| /// A class used to test the performance of the analyzer. |
| class AnalysisTester { |
| /// The number of data points to collect. |
| static const int dataPointCount = 50; |
| |
| /// The in-memory file system to which files used by the tests are written. |
| final ResourceProvider provider; |
| |
| /// The path to the location of the SDK. |
| final String sdkPath; |
| |
| /// The path to the location of the directory containing the test packages. |
| final String packageRootPath; |
| |
| /// The paths to the packages being used in this test. |
| final Set<String> packagePaths = {}; |
| |
| /// Initialize an analysis tester that uses a memory resource provider. |
| factory memory() { |
| var provider = MemoryResourceProvider(); |
| var sdkPath = '/sdk'; |
| var packageRootPath = '/test/pkgs'; |
| createMockSdk( |
| resourceProvider: provider, |
| root: provider.getFolder(sdkPath), |
| ); |
| return AnalysisTester._( |
| provider: provider, |
| sdkPath: sdkPath, |
| packageRootPath: packageRootPath, |
| ); |
| } |
| |
| /// Initialize an analysis tester that uses a physical resource provider. |
| factory physical() { |
| var provider = PhysicalResourceProvider(); |
| var sdkPath = '/Users/brianwilkerson/dart-sdk'; |
| var packageRootPath = '/Users/brianwilkerson/src/dart/samples/Overmorrow'; |
| return AnalysisTester._( |
| provider: provider, |
| sdkPath: sdkPath, |
| packageRootPath: packageRootPath, |
| ); |
| } |
| |
| /// Initialize an analysis tester that uses the given resource [provider], |
| /// [sdkPath], and [packagePath]. |
| new _({ |
| required this.provider, |
| required this.sdkPath, |
| required this.packageRootPath, |
| }); |
| |
| /// Returns the average time in microseconds to analyze the package. |
| /// |
| /// The package will be analyzed [runCount] times in order to compute the |
| /// average. |
| Future<double> averageTimeToAnalyzePackages({int runCount = 5}) async { |
| var duration = 0; |
| for (var i = 0; i < runCount; i++) { |
| duration += await timeToAnalyzePackages(); |
| } |
| return duration / runCount; |
| } |
| |
| /// Returns the name of the [index]th file. |
| String fileName(int index) => 'test$index.dart'; |
| |
| /// Returns the path of the [index]th file in the package at the given |
| /// [packagePath]. |
| String filePath(String packagePath, int index) => |
| '$packagePath/lib/${fileName(index)}'; |
| |
| /// Returns the path of the [index]th package. |
| /// |
| /// Accessing a package path will add it to the list of paths to be analyzed. |
| String packagePath(int index) { |
| var packagePath = '$packageRootPath/package$index'; |
| packagePaths.add(packagePath); |
| return packagePath; |
| } |
| |
| /// Returns the number of microseconds to analyze the package one time. |
| /// |
| /// If [printPerformanceData] is `true` then information about the performance |
| /// of the analyzer will be printed. |
| /// |
| /// If [printStats] is `true` then statistics about the code being analyzed |
| /// will be printed. |
| Future<int> timeToAnalyzePackages({ |
| bool printPerformanceData = false, |
| bool printStats = false, |
| }) async { |
| var log = PerformanceLog(null); |
| var scheduler = AnalysisDriverScheduler(log); |
| var collection = AnalysisContextCollectionImpl( |
| resourceProvider: provider, |
| sdkPath: sdkPath, |
| includedPaths: packagePaths.toList(), |
| scheduler: scheduler, |
| performanceLog: log, |
| withFineDependencies: true, |
| ); |
| for (var context in collection.contexts) { |
| var contextRoot = context.contextRoot; |
| var analyzedFiles = contextRoot.analyzedFiles(); |
| var driver = context.driver; |
| for (var analyzedFilePath in analyzedFiles) { |
| driver.addFile(analyzedFilePath); |
| } |
| } |
| |
| var startTime = DateTime.now().microsecondsSinceEpoch; |
| scheduler.start(); |
| await scheduler.waitForIdle(); |
| var stopTime = DateTime.now().microsecondsSinceEpoch; |
| |
| if (printStats) { |
| _printStats(collection); |
| } |
| |
| if (printPerformanceData) { |
| _printPerformanceData(collection); |
| } |
| |
| for (var context in collection.contexts) { |
| await context.driver.dispose2(); |
| } |
| |
| return stopTime - startTime; |
| } |
| |
| void _printPerformanceData(AnalysisContextCollectionImpl collection) { |
| print(''); |
| var buffer = StringBuffer(); |
| collection.scheduler.accumulatedPerformance.write(buffer: buffer); |
| print(buffer); |
| } |
| |
| void _printStats(AnalysisContextCollectionImpl collection) { |
| var contexts = collection.contexts; |
| print('Produced ${contexts.length} analysis contexts.'); |
| |
| var totalFileCount = 0; |
| var totalLineCount = 0; |
| var totalCycleCount = 0; |
| for (var context in contexts) { |
| var contextRoot = context.contextRoot; |
| var driver = context.driver; |
| var includedPaths = contextRoot.includedPaths; |
| var sourceFactory = driver.sourceFactory; |
| var fileCount = 0; |
| var lineCount = 0; |
| var cycles = <LibraryCycle>{}; |
| for (var analyzedFilePath in contextRoot.analyzedFiles()) { |
| var pathContext2 = collection.resourceProvider.pathContext; |
| if (!file_paths.isDart(pathContext2, analyzedFilePath)) continue; |
| |
| fileCount++; |
| var fileState = driver.fsState.getExistingFromPath(analyzedFilePath); |
| if (fileState == null) { |
| print('Missing file state for "$analyzedFilePath".'); |
| continue; |
| } |
| lineCount += fileState.lineInfo.lineCount; |
| var libraryCycle = fileState.kind.library?.libraryCycle; |
| if (libraryCycle != null) { |
| cycles.add(libraryCycle); |
| } |
| } |
| totalFileCount += fileCount; |
| totalLineCount += lineCount; |
| var cycleCount = cycles.length; |
| totalCycleCount += cycleCount; |
| |
| print(''); |
| print('Context'); |
| if (includedPaths.length == 1) { |
| print(' Included directory: ${includedPaths.first}'); |
| } else { |
| print(' Included directories:'); |
| for (var directory in includedPaths) { |
| print(' - $directory'); |
| } |
| } |
| print(' Number of files: $fileCount'); |
| print(' Number of lines: $lineCount'); |
| print(' Number of cycles: $cycleCount'); |
| print(' Package map length: ${sourceFactory.packageMap?.length}'); |
| // _printFileParseCounts(driver.fsState.fileParseCounts()); |
| } |
| print(''); |
| print('Total number of files: $totalFileCount'); |
| print('Total number of lines: $totalLineCount'); |
| print('Total number of cycles: $totalCycleCount'); |
| } |
| } |