// 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 'package:analyzer/file_system/overlay_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/byte_store.dart'; import 'package:analyzer/src/dart/analysis/driver.dart'; import 'package:analyzer/src/dart/analysis/file_content_cache.dart'; import 'package:analyzer/src/util/performance/operation_performance.dart'; import 'package:collection/collection.dart'; import 'package:linter/src/rules.dart'; void main() async { const repeatCount = 2; const planRepeatCount = 2; var planCollection = planCollectionFlutter; registerLintRules(); var updateIndex = 0; for (var withFine in List.filled(repeatCount, [false, true]).flattened) { var resourceProvider = OverlayResourceProvider( PhysicalResourceProvider.INSTANCE, ); var collection = AnalysisContextCollectionImpl( resourceProvider: resourceProvider, sdkPath: Paths.sdkRun, includedPaths: planCollection.includedPaths, byteStore: MemoryByteStore(), fileContentCache: FileContentCache(resourceProvider), withFineDependencies: withFine, ); // Add all Dart files. for (var analysisContext in collection.contexts) { for (var path in analysisContext.contextRoot.analyzedFiles()) { if (path.endsWith('.dart')) { analysisContext.driver.addFile(path); } } } var initialAnalysisTimer = Stopwatch()..start(); collection.scheduler.resetAccumulatedPerformance(); await collection.scheduler.waitForIdle(); await pumpEventQueue(); initialAnalysisTimer.stop(); (withFine ? planCollection.initialAnalysisWithFineTrue : planCollection.initialAnalysisWithFineFalse) .add(initialAnalysisTimer.elapsed); { print('\n' * 3); print( '[withFine: $withFine] Initial analysis, ' '${initialAnalysisTimer.elapsedMilliseconds} ms', ); print('-' * 64); var buffer = StringBuffer(); collection.scheduler.accumulatedPerformance.write(buffer: buffer); print(buffer.toString().trim()); } for (var plan in planCollection.plans) { var targetPath = plan.filePath; var targetCode = PhysicalResourceProvider.INSTANCE .getFile(targetPath) .readAsStringSync(); for (var i = 0; i < planRepeatCount; i++) { // Update. var replacement = plan.replacementTemplate.replaceAll( '#[UI]', '${updateIndex++}', ); resourceProvider.setOverlay( targetPath, content: targetCode.replaceAll(plan.searchText, replacement), modificationStamp: 0, ); for (var analysisContext in collection.contexts) { analysisContext.changeFile(targetPath); } // Measure. var timer = Stopwatch()..start(); collection.scheduler.resetAccumulatedPerformance(); await collection.scheduler.waitForIdle(); await pumpEventQueue(); { print('\n' * 3); print('[withFine: $withFine][$i] $targetPath'); print(' searchText: ${plan.searchText}'); print(' replacement: $replacement'); var elapsed = timer.elapsed; print(' timer: ${elapsed.inMilliseconds} ms'); print('-' * 64); (withFine ? plan.withFineTrue : plan.withFineFalse).add(elapsed); var buffer = StringBuffer(); collection.scheduler.accumulatedPerformance.write(buffer: buffer); print(buffer.toString().trim()); } // Revert. { resourceProvider.setOverlay( targetPath, content: targetCode, modificationStamp: 1, ); for (var analysisContext in collection.contexts) { analysisContext.changeFile(targetPath); } await collection.scheduler.waitForIdle(); await pumpEventQueue(); print('-' * 32); print('[reverted][waitForIdle]'); } } } } print('\n' * 3); print('${'-' * 64} results'); _printDurations( 'Initial analysis', planCollection.initialAnalysisWithFineFalse, planCollection.initialAnalysisWithFineTrue, ); print(''); for (var plan in planCollection.plans) { _printDurations(plan.filePath, plan.withFineFalse, plan.withFineTrue); print(''); } print('\n' * 2); } final planCollectionAnalyzer = PlanCollection( includedPaths: [Paths.sdkAnalyzer], plans: [ Plan( filePath: '${Paths.sdkAnalyzer}/lib/src/fine/library_manifest.dart', searchText: 'computeManifests({', replacementTemplate: 'computeManifests#[UI]({', ), ], ); final planCollectionFlutter = PlanCollection( includedPaths: [Paths.flutterPackage], plans: [ Plan( filePath: '${Paths.flutterPackage}/lib/src/foundation/memory_allocations.dart', searchText: 'dispatchObjectEvent(ObjectEvent event) {', replacementTemplate: 'dispatchObjectEvent#[UI](ObjectEvent event) {', ), Plan( filePath: '${Paths.flutterPackage}/lib/src/painting/image_cache.dart', searchText: 'containsKey(Object key) {', replacementTemplate: 'containsKey#[UI](Object key) {', ), Plan( filePath: '${Paths.flutterPackage}/lib/src/widgets/banner.dart', searchText: 'shouldRepaint(BannerPainter oldDelegate) {', replacementTemplate: 'shouldRepaint#[UI](BannerPainter oldDelegate) {', ), ], ); Future pumpEventQueue([int times = 5000]) { if (times == 0) return Future.value(); return Future.delayed(Duration.zero, () => pumpEventQueue(times - 1)); } String _formatFineDelta( Durations fineFalseDurations, Durations fineTrueDurations, ) { var baseMs = fineFalseDurations.best.inMilliseconds; var fineMs = fineTrueDurations.best.inMilliseconds; if (baseMs == 0 && fineMs == 0) { return ' fine-grained: undefined (time $baseMs → $fineMs ms)'; } var deltaMs = fineMs - baseMs; if (deltaMs == 0) { return ' fine-grained: no change (time $baseMs → $fineMs ms)'; } var percent = (deltaMs / baseMs) * 100; var percentStr = '${percent >= 0 ? '+' : ''}${percent.toStringAsFixed(1)}%'; var timeStr = 'time $baseMs → $fineMs ms'; if (fineMs < baseMs) { var ratio = baseMs / fineMs; return ' fine-grained: ${ratio.toStringAsFixed(1)}× faster ' '($timeStr; $percentStr)'; } else { var ratio = fineMs / baseMs; return ' fine-grained: ${ratio.toStringAsFixed(1)}× slower ' '($timeStr; $percentStr)'; } } void _printDurations( String title, Durations fineFalseDurations, Durations fineTrueDurations, ) { print(title); print(fineFalseDurations.format('[withFine: false]')); print(fineTrueDurations.format('[withFine: true ]')); print(_formatFineDelta(fineFalseDurations, fineTrueDurations)); } class Durations { final List values = []; Duration get best { if (values.isEmpty) { return Duration.zero; } return values.min; } void add(Duration value) { values.add(value); } String format(String title) { return ' $title, ' 'best: ${best.inMilliseconds} ms, ' 'all: ${values.map((e) => e.inMilliseconds).toList()}'; } } class Paths { static const sdkRun = '/Users/scheglov/Applications/dart-sdk'; static const sdkRepo = '/Users/scheglov/Source/Dart/sdk.git/sdk'; static const sdkAnalyzer = '$sdkRepo/pkg/analyzer'; static const sdkAnalysisServer = '$sdkRepo/pkg/analysis_server'; static const sdkLinter = '$sdkRepo/pkg/linter'; static const flutterRepo = '/Users/scheglov/Source/flutter'; static const flutterPackage = '$flutterRepo/packages/flutter'; } class Plan { final String filePath; final String searchText; final String replacementTemplate; final Durations withFineFalse = Durations(); final Durations withFineTrue = Durations(); Plan({ required this.filePath, required this.searchText, required this.replacementTemplate, }); } class PlanCollection { final List includedPaths; final List plans; final Durations initialAnalysisWithFineFalse = Durations(); final Durations initialAnalysisWithFineTrue = Durations(); PlanCollection({required this.includedPaths, required this.plans}); } extension AnalysisDriverSchedulerPerformance on AnalysisDriverScheduler { /// Reset the accumulated scheduler performance to a fresh operation. void resetAccumulatedPerformance() { accumulatedPerformance = OperationPerformanceImpl(''); } }