| // Copyright (c) 2016, 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. | 
 |  | 
 | /// An entrypoint used to run portions of analyzer and measure its performance. | 
 | /// | 
 | /// TODO(sigmund): rename to 'analyzer_perf.dart' in sync with changes to the | 
 | /// perf bots. | 
 | /// | 
 | /// This file was started to measure the implementation of the front-end when it | 
 | /// was based on the analyzer codebase.  Now that we are using fasta as the | 
 | /// implementation (which is measured in fasta_perf.dart), we still want to | 
 | /// measure the analyzer to ensure that there are no regressions when replacing | 
 | /// features (e.g. there is no regression from replacing summaries with kernel | 
 | /// outlines). | 
 | library front_end.tool.perf; | 
 |  | 
 | import 'dart:io' show Directory, File, Platform, exit; | 
 |  | 
 | import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'; | 
 | import 'package:analyzer/dart/analysis/features.dart'; | 
 | import 'package:analyzer/dart/ast/ast.dart'; | 
 | import 'package:analyzer/error/listener.dart'; | 
 | import 'package:analyzer/file_system/file_system.dart' show Folder; | 
 | import 'package:analyzer/file_system/physical_file_system.dart'; | 
 | import 'package:analyzer/src/context/packages.dart'; | 
 | import 'package:analyzer/src/dart/sdk/sdk.dart' show FolderBasedDartSdk; | 
 | import 'package:analyzer/src/file_system/file_system.dart'; | 
 | import 'package:analyzer/src/generated/parser.dart'; | 
 | import 'package:analyzer/src/generated/source.dart'; | 
 | import 'package:analyzer/src/source/package_map_resolver.dart'; | 
 | import 'package:path/path.dart' as path; | 
 |  | 
 | Future<void> main(List<String> args) async { | 
 |   // TODO(sigmund): provide sdk folder as well. | 
 |   if (args.length < 2) { | 
 |     print('usage: perf.dart <bench-id> <entry.dart>'); | 
 |     exit(1); | 
 |   } | 
 |  | 
 |   var bench = args[0]; | 
 |   var entryUri = Uri.base.resolve(args[1]); | 
 |  | 
 |   setup(path.fromUri(entryUri)); | 
 |  | 
 |   Set<Source> files = scanReachableFiles(entryUri); | 
 |   var handlers = { | 
 |     'scan': () async => scanFiles(files), | 
 |     'parse': () async => parseFiles(files), | 
 |   }; | 
 |  | 
 |   var handler = handlers[bench]; | 
 |   if (handler == null) { | 
 |     print('unsupported bench-id: $bench. Please specify one of the following: ' | 
 |         '${handlers.keys.join(", ")}'); | 
 |     exit(1); | 
 |   } | 
 |  | 
 |   // TODO(sigmund): replace the warmup with instrumented snapshots. | 
 |   int iterations = bench.contains('kernel_gen') ? 2 : 10; | 
 |   for (int i = 0; i < iterations; i++) { | 
 |     var totalTimer = new Stopwatch()..start(); | 
 |     print('== iteration $i'); | 
 |     await handler(); | 
 |     totalTimer.stop(); | 
 |     report('total', totalTimer.elapsedMicroseconds); | 
 |   } | 
 | } | 
 |  | 
 | /// Cumulative time spent parsing. | 
 | Stopwatch parseTimer = new Stopwatch(); | 
 |  | 
 | /// Cumulative time spent scanning. | 
 | Stopwatch scanTimer = new Stopwatch(); | 
 |  | 
 | /// Size of all sources. | 
 | int inputSize = 0; | 
 |  | 
 | /// Factory to load and resolve app, packages, and sdk sources. | 
 | late SourceFactory sources; | 
 |  | 
 | /// Path to the root of the built SDK that is being used to execute this script. | 
 | final _sdkPath = _findSdkPath(); | 
 |  | 
 | /// Add to [files] all sources reachable from [start]. | 
 | void collectSources(Source start, Set<Source> files) { | 
 |   if (!files.add(start)) return; | 
 |   var unit = parseDirectives(start); | 
 |   for (var directive in unit.directives) { | 
 |     if (directive is UriBasedDirective) { | 
 |       var next = sources.resolveUri(start, directive.uri.stringValue)!; | 
 |       collectSources(next, files); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | /// Uses the diet-parser to parse only directives in [source]. | 
 | CompilationUnit parseDirectives(Source source) { | 
 |   var result = tokenize(source); | 
 |   var lineInfo = LineInfo(result.lineStarts); | 
 |   var parser = new Parser( | 
 |     source, | 
 |     AnalysisErrorListener.NULL_LISTENER, | 
 |     featureSet: FeatureSet.latestLanguageVersion(), | 
 |     lineInfo: lineInfo, | 
 |   ); | 
 |   return parser.parseDirectives(result.tokens); | 
 | } | 
 |  | 
 | /// Parses every file in [files] and reports the time spent doing so. | 
 | void parseFiles(Set<Source> files) { | 
 |   scanTimer = new Stopwatch(); | 
 |   parseTimer = new Stopwatch(); | 
 |   for (var source in files) { | 
 |     parseFull(source); | 
 |   } | 
 |  | 
 |   report('scan', scanTimer.elapsedMicroseconds); | 
 |   report('parse', parseTimer.elapsedMicroseconds); | 
 | } | 
 |  | 
 | /// Parse the full body of [source] and return it's compilation unit. | 
 | CompilationUnit parseFull(Source source) { | 
 |   var result = tokenize(source); | 
 |   var lineInfo = LineInfo(result.lineStarts); | 
 |   parseTimer.start(); | 
 |   var parser = new Parser( | 
 |     source, | 
 |     AnalysisErrorListener.NULL_LISTENER, | 
 |     featureSet: FeatureSet.latestLanguageVersion(), | 
 |     lineInfo: lineInfo, | 
 |   ); | 
 |   var unit = parser.parseCompilationUnit(result.tokens); | 
 |   parseTimer.stop(); | 
 |   return unit; | 
 | } | 
 |  | 
 | /// Report that metric [name] took [time] micro-seconds to process | 
 | /// [inputSize] characters. | 
 | void report(String name, int time) { | 
 |   StringBuffer sb = new StringBuffer(); | 
 |   String padding = ' ' * (20 - name.length); | 
 |   sb.write('$name:$padding $time us, ${time ~/ 1000} ms'); | 
 |   String invSpeed = (time * 1000 / inputSize).toStringAsFixed(2); | 
 |   sb.write(', $invSpeed ns/char'); | 
 |   print('$sb'); | 
 | } | 
 |  | 
 | /// Scans every file in [files] and reports the time spent doing so. | 
 | void scanFiles(Set<Source> files) { | 
 |   // `tokenize` records how many chars are scanned and how long it takes to scan | 
 |   // them. As this function is called repeatedly when running as a benchmark, we | 
 |   // make sure to clear the data and compute it again every time. | 
 |   scanTimer = new Stopwatch(); | 
 |   for (var source in files) { | 
 |     tokenize(source); | 
 |   } | 
 |  | 
 |   report('scan', scanTimer.elapsedMicroseconds); | 
 | } | 
 |  | 
 | /// Load and scans all files we need to process: files reachable from the | 
 | /// entrypoint and all core libraries automatically included by the VM. | 
 | Set<Source> scanReachableFiles(Uri entryUri) { | 
 |   var files = new Set<Source>(); | 
 |   var loadTimer = new Stopwatch()..start(); | 
 |   scanTimer = new Stopwatch(); | 
 |   collectSources(sources.forUri2(entryUri)!, files); | 
 |  | 
 |   var libs = [ | 
 |     'dart:async', | 
 |     'dart:collection', | 
 |     'dart:convert', | 
 |     'dart:core', | 
 |     'dart:developer', | 
 |     'dart:_internal', | 
 |     'dart:isolate', | 
 |     'dart:math', | 
 |     'dart:mirrors', | 
 |     'dart:typed_data', | 
 |     'dart:io' | 
 |   ]; | 
 |  | 
 |   for (var lib in libs) { | 
 |     collectSources(sources.forUri(lib)!, files); | 
 |   } | 
 |  | 
 |   loadTimer.stop(); | 
 |  | 
 |   inputSize = 0; | 
 |   for (var s in files) { | 
 |     inputSize += s.contents.data.length; | 
 |   } | 
 |   print('input size: ${inputSize} chars'); | 
 |   var loadTime = loadTimer.elapsedMicroseconds - scanTimer.elapsedMicroseconds; | 
 |   report('load', loadTime); | 
 |   report('scan', scanTimer.elapsedMicroseconds); | 
 |   return files; | 
 | } | 
 |  | 
 | /// Sets up analyzer to be able to load and resolve app, packages, and sdk | 
 | /// sources. | 
 | void setup(String path) { | 
 |   var provider = PhysicalResourceProvider.INSTANCE; | 
 |  | 
 |   var packages = findPackagesFrom( | 
 |     provider, | 
 |     provider.getResource(path), | 
 |   ); | 
 |  | 
 |   var packageMap = <String, List<Folder>>{}; | 
 |   for (var package in packages.packages) { | 
 |     packageMap[package.name] = [package.libFolder]; | 
 |   } | 
 |  | 
 |   sources = new SourceFactory([ | 
 |     new ResourceUriResolver(provider), | 
 |     new PackageMapUriResolver(provider, packageMap), | 
 |     new DartUriResolver( | 
 |         new FolderBasedDartSdk(provider, provider.getFolder(_sdkPath))), | 
 |   ]); | 
 | } | 
 |  | 
 | /// Scan [source] and return the first token produced by the scanner. | 
 | ScannerResult tokenize(Source source) { | 
 |   scanTimer.start(); | 
 |   // TODO(sigmund): is there a way to scan from a random-access-file without | 
 |   // first converting to String? | 
 |   ScannerResult result = | 
 |       scanString(source.contents.data, includeComments: false); | 
 |   var token = result.tokens; | 
 |   if (result.hasErrors) { | 
 |     // Ignore errors. | 
 |     while (token is ErrorToken) { | 
 |       token = token.next!; | 
 |     } | 
 |   } | 
 |   scanTimer.stop(); | 
 |   return result; | 
 | } | 
 |  | 
 | String _findSdkPath() { | 
 |   var executable = Platform.resolvedExecutable; | 
 |   var executableDir = path.dirname(executable); | 
 |   for (var candidate in [ | 
 |     path.dirname(executableDir), | 
 |     path.join(executableDir, 'dart-sdk') | 
 |   ]) { | 
 |     if (new File(path.join(candidate, 'lib', 'libraries.json')).existsSync()) { | 
 |       return candidate; | 
 |     } | 
 |   } | 
 |   // Not found; guess "sdk" relative to the current directory. | 
 |   return new Directory('sdk').absolute.path; | 
 | } |