| // 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. | |
| library; | |
| import 'dart:io' show exit; | |
| import 'package:analyzer/dart/analysis/features.dart'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/token.dart'; | |
| import 'package:analyzer/dart/element/element.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' | |
| show PhysicalResourceProvider; | |
| import 'package:analyzer/source/line_info.dart'; | |
| import 'package:analyzer/source/source.dart'; | |
| import 'package:analyzer/src/context/packages.dart'; | |
| import 'package:analyzer/src/dart/analysis/experiments.dart'; | |
| import 'package:analyzer/src/dart/scanner/scanner.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' | |
| show DartUriResolver, SourceFactory; | |
| import 'package:analyzer/src/source/package_map_resolver.dart'; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| 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 totalTimer = Stopwatch()..start(); | |
| var bench = args[0]; | |
| var entryUri = Uri.base.resolve(args[1]); | |
| await setup(args[1]); | |
| if (bench == 'scan') { | |
| var files = scanReachableFiles(entryUri); | |
| // TODO(sigmund): consider replacing the warmup with instrumented snapshots. | |
| for (var i = 0; i < 10; i++) { | |
| scanFiles(files); | |
| } | |
| } else if (bench == 'parse') { | |
| var files = scanReachableFiles(entryUri); | |
| // TODO(sigmund): consider replacing the warmup with instrumented snapshots. | |
| for (var i = 0; i < 10; i++) { | |
| parseFiles(files); | |
| } | |
| } else { | |
| print('unsupported bench-id: $bench. Please specify "scan" or "parse"'); | |
| // TODO(sigmund): implement the remaining benchmarks. | |
| exit(1); | |
| } | |
| totalTimer.stop(); | |
| report('total', totalTimer.elapsedMicroseconds); | |
| } | |
| /// Cumulative time spent scanning. | |
| Stopwatch scanTimer = Stopwatch(); | |
| /// Cumulative total number of chars scanned. | |
| int scanTotalChars = 0; | |
| /// Factory to load and resolve app, packages, and sdk sources. | |
| late SourceFactory sources; | |
| /// 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 languageVersion = LibraryLanguageVersion( | |
| package: ExperimentStatus.currentVersion, | |
| override: result.overrideVersion, | |
| ); | |
| var lineInfo = LineInfo(result.lineStarts); | |
| var parser = Parser( | |
| DiagnosticReporter(DiagnosticListener.nullListener, source), | |
| featureSet: FeatureSet.latestLanguageVersion(), | |
| lineInfo: lineInfo, | |
| languageVersion: languageVersion, | |
| ); | |
| return parser.parseDirectives(result.tokens); | |
| } | |
| /// Parses every file in [files] and reports the time spent doing so. | |
| void parseFiles(Set<Source?> files) { | |
| // The code below will record again how many chars are scanned and how long it | |
| // takes to scan them, even though we already did so in [scanReachableFiles]. | |
| // Recording and reporting this twice is unnecessary, but we do so for now to | |
| // validate that the results are consistent. | |
| scanTimer = Stopwatch(); | |
| var old = scanTotalChars; | |
| scanTotalChars = 0; | |
| var parseTimer = Stopwatch()..start(); | |
| for (var source in files) { | |
| parseFull(source!); | |
| } | |
| parseTimer.stop(); | |
| // Report size and scanning time again. See discussion above. | |
| if (old != scanTotalChars) print('input size changed? $old chars'); | |
| report('scan', scanTimer.elapsedMicroseconds); | |
| var pTime = parseTimer.elapsedMicroseconds - scanTimer.elapsedMicroseconds; | |
| report('parse', pTime); | |
| } | |
| /// Parse the full body of [source] and return it's compilation unit. | |
| CompilationUnit parseFull(Source source) { | |
| var result = tokenize(source); | |
| var languageVersion = LibraryLanguageVersion( | |
| package: ExperimentStatus.currentVersion, | |
| override: result.overrideVersion, | |
| ); | |
| var lineInfo = LineInfo(result.lineStarts); | |
| var parser = Parser( | |
| DiagnosticReporter(DiagnosticListener.nullListener, source), | |
| featureSet: FeatureSet.latestLanguageVersion(), | |
| languageVersion: languageVersion, | |
| lineInfo: lineInfo, | |
| ); | |
| return parser.parseCompilationUnit(result.tokens); | |
| } | |
| /// Report that metric [name] took [time] micro-seconds to process | |
| /// [scanTotalChars] characters. | |
| void report(String name, int time) { | |
| var sb = StringBuffer(); | |
| sb.write('$name: $time us, ${time ~/ 1000} ms'); | |
| sb.write(', ${scanTotalChars * 1000 ~/ time} chars/ms'); | |
| print('$sb'); | |
| } | |
| /// Scans every file in [files] and reports the time spent doing so. | |
| void scanFiles(Set<Source?> files) { | |
| // The code below will record again how many chars are scanned and how long it | |
| // takes to scan them, even though we already did so in [scanReachableFiles]. | |
| // Recording and reporting this twice is unnecessary, but we do so for now to | |
| // validate that the results are consistent. | |
| scanTimer = Stopwatch(); | |
| var old = scanTotalChars; | |
| scanTotalChars = 0; | |
| for (var source in files) { | |
| tokenize(source!); | |
| } | |
| // Report size and scanning time again. See discussion above. | |
| if (old != scanTotalChars) print('input size changed? $old chars'); | |
| 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 = <Source?>{}; | |
| var loadTimer = Stopwatch()..start(); | |
| collectSources(sources.forUri2(entryUri), files); | |
| var libs = [ | |
| 'dart:async', | |
| 'dart:cli', | |
| '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(); | |
| print('input size: $scanTotalChars 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. | |
| Future setup(String path) async { | |
| var provider = PhysicalResourceProvider.INSTANCE; | |
| var packages = findPackagesFrom(provider, provider.getResource(path)); | |
| var packageMap = <String, Folder>{}; | |
| for (var package in packages.packages) { | |
| packageMap[package.name] = package.libFolder; | |
| } | |
| sources = SourceFactory([ | |
| ResourceUriResolver(provider), | |
| PackageMapUriResolver(provider, packageMap), | |
| DartUriResolver(FolderBasedDartSdk(provider, provider.getFolder('sdk'))), | |
| ]); | |
| } | |
| /// Scan [source] and return the first token produced by the scanner. | |
| ScannerResult tokenize(Source source) { | |
| scanTimer.start(); | |
| var contents = source.contents.data; | |
| scanTotalChars += contents.length; | |
| // TODO(paulberry): figure out the appropriate featureSet to use here | |
| var featureSet = FeatureSet.latestLanguageVersion(); | |
| // TODO(sigmund): is there a way to scan from a random-access-file without | |
| // first converting to String? | |
| var scanner = Scanner(inputText: contents, reportError: (_) {}) | |
| ..configureFeatures( | |
| featureSetForOverriding: featureSet, | |
| featureSet: featureSet, | |
| ) | |
| ..preserveComments = false; | |
| var token = scanner.tokenize(); | |
| scanTimer.stop(); | |
| return ScannerResult(token, scanner.lineStarts, scanner.overrideVersion); | |
| } | |
| class ScannerResult { | |
| final Token tokens; | |
| final List<int> lineStarts; | |
| final Version? overrideVersion; | |
| new(this.tokens, this.lineStarts, this.overrideVersion); | |
| } |