// 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:async'; import 'dart:io' show Directory, File, Platform, exit; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/error/listener.dart'; import 'package:analyzer/file_system/physical_file_system.dart' show PhysicalResourceProvider; import 'package:analyzer/source/package_map_resolver.dart'; import 'package:analyzer/src/context/builder.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/generated/source_io.dart'; import 'package:analyzer/src/summary/format.dart'; import 'package:analyzer/src/summary/idl.dart'; import 'package:analyzer/src/summary/link.dart'; import 'package:analyzer/src/summary/summarize_ast.dart'; import 'package:front_end/src/scanner/reader.dart'; import 'package:front_end/src/scanner/scanner.dart'; import 'package:front_end/src/scanner/token.dart'; import 'package:package_config/discovery.dart'; import 'package:path/path.dart' as path; main(List args) async { // TODO(sigmund): provide sdk folder as well. if (args.length < 2) { print('usage: perf.dart '); exit(1); } var bench = args[0]; var entryUri = Uri.base.resolve(args[1]); await setup(entryUri); Set files = scanReachableFiles(entryUri); var handlers = { 'scan': () async => scanFiles(files), 'parse': () async => parseFiles(files), 'unlinked_summarize': () async => summarize(files), 'unlinked_summarize_from_sources': () async => summarize(files), 'prelinked_summarize': () async => summarize(files, prelink: true), 'linked_summarize': () async => summarize(files, link: true), }; 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 building unlinked summaries. Stopwatch unlinkedSummarizeTimer = 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. 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 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); } } } /// Generates unlinkmed summaries for all files in [files], and returns them in /// an [_UnlinkedSummaries] container. _UnlinkedSummaries generateUnlinkedSummaries(Set files) { var unlinkedSummaries = new _UnlinkedSummaries(); for (var source in files) { unlinkedSummaries.summariesByUri[source.uri.toString()] = unlinkedSummarize(source); } return unlinkedSummaries; } /// Generates unlinked summaries for every file in [files] and, if requested via /// [prelink] or [link], generates the pre-linked and linked summaries as well. /// /// This function also prints a report of the time spent on each action. void summarize(Set files, {bool prelink: false, bool link: false}) { scanTimer = new Stopwatch(); parseTimer = new Stopwatch(); unlinkedSummarizeTimer = new Stopwatch(); var unlinkedSummaries = generateUnlinkedSummaries(files); report('scan', scanTimer.elapsedMicroseconds); report('parse', parseTimer.elapsedMicroseconds); report('unlinked_summarize', unlinkedSummarizeTimer.elapsedMicroseconds); report( 'unlinked_summarize_from_sources', unlinkedSummarizeTimer.elapsedMicroseconds + parseTimer.elapsedMicroseconds + scanTimer.elapsedMicroseconds); if (prelink || link) { var prelinkTimer = new Stopwatch()..start(); var prelinkedLibraries = prelinkSummaries(files, unlinkedSummaries); prelinkTimer.stop(); report('prelinked_summarize', prelinkTimer.elapsedMicroseconds); if (link) { var linkTimer = new Stopwatch()..start(); LinkedLibrary getDependency(String uri) { // getDependency should never be called because all dependencies are // present in [prelinkedLibraries]. print('Warning: getDependency called for: $uri'); return null; } relink(prelinkedLibraries, getDependency, unlinkedSummaries.getUnit, true /*strong*/); linkTimer.stop(); report('linked_summarize', linkTimer.elapsedMicroseconds); } } } /// Uses the diet-parser to parse only directives in [source]. CompilationUnit parseDirectives(Source source) { var token = tokenize(source); var parser = new Parser(source, AnalysisErrorListener.NULL_LISTENER); return parser.parseDirectives(token); } /// Parses every file in [files] and reports the time spent doing so. void parseFiles(Set 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 token = tokenize(source); parseTimer.start(); var parser = new Parser(source, AnalysisErrorListener.NULL_LISTENER); var unit = parser.parseCompilationUnit(token); parseTimer.stop(); return unit; } /// Prelinks all the summaries for [files], using [unlinkedSummaries] to obtain /// their unlinked summaries. /// /// The return value is suitable for passing to the summary linker. Map prelinkSummaries( Set files, _UnlinkedSummaries unlinkedSummaries) { Set libraryUris = files.map((source) => '${source.uri}').toSet(); String getDeclaredVariable(String s) => null; var prelinkedLibraries = setupForLink(libraryUris, unlinkedSummaries.getUnit, getDeclaredVariable); return prelinkedLibraries; } /// Report that metric [name] took [time] micro-seconds to process /// [inputSize] characters. void report(String name, int time) { var sb = new StringBuffer(); var padding = ' ' * (20 - name.length); sb.write('$name:$padding $time us, ${time ~/ 1000} ms'); var 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 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 scanReachableFiles(Uri entryUri) { var files = new Set(); 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. Future setup(Uri entryUri) async { var provider = PhysicalResourceProvider.INSTANCE; var packageMap = new ContextBuilder(provider, null, null) .convertPackagesToMap(await findPackages(entryUri)); 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. Token tokenize(Source source) { scanTimer.start(); // TODO(sigmund): is there a way to scan from a random-access-file without // first converting to String? var scanner = new _Scanner(source.contents.data); var token = scanner.tokenize(); scanTimer.stop(); return token; } UnlinkedUnitBuilder unlinkedSummarize(Source source) { var unit = parseFull(source); unlinkedSummarizeTimer.start(); var unlinkedUnit = serializeAstUnlinked(unit); unlinkedSummarizeTimer.stop(); return unlinkedUnit; } 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', 'dart_server.platform')) .existsSync()) { return candidate; } } // Not found; guess "sdk" relative to the current directory. return new Directory('sdk').absolute.path; } /// Simple container for a mapping from URI string to an unlinked summary. class _UnlinkedSummaries { final summariesByUri = {}; /// Get the unlinked summary for the given URI, and report a warning if it /// can't be found. UnlinkedUnit getUnit(String uri) { var result = summariesByUri[uri]; if (result == null) { print('Warning: no summary found for: $uri'); } return result; } } class _Scanner extends Scanner { _Scanner(String contents) : super.create(new CharSequenceReader(contents)) { preserveComments = false; } @override void reportError(errorCode, int offset, List arguments) { // ignore errors. } }