|  | // Copyright (c) 2017, 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 measure performance of the incremental compiler. | 
|  | /// | 
|  | /// Given an input a program and a .json file describing edits, this script will | 
|  | /// first compile the program, then apply edits, recompile the | 
|  | /// program, and report relevant metrics. | 
|  | /// | 
|  | /// The edits are encoded as a JSON array: | 
|  | ///  - Each entry in the array is an iteration of edits and holds a list of | 
|  | ///  individual edits. All changes in one iteration are applied at once | 
|  | ///  before calling [IncrementalKernelGenerator.computeDelta]. | 
|  | /// | 
|  | ///  - Each edit is a triple declaring a string replacement operation: | 
|  | ///       [uri, from, to] | 
|  | /// | 
|  | ///    Edits are applied in order, so more than on edit is allowed on the same | 
|  | ///    file. | 
|  | /// | 
|  | ///  For example: | 
|  | ///  [ | 
|  | ///    { | 
|  | ///      "name" : "big_change", | 
|  | ///      "edits" : [ | 
|  | ///        ["input1.dart", "black", "green"], | 
|  | ///        ["input1.dart", "30px", "10px"], | 
|  | ///        ["input2.dart", "a.toString()", ""$a""] | 
|  | ///      ] | 
|  | ///    }, | 
|  | ///    { | 
|  | ///      "name" : "small_change", | 
|  | ///      "edits" : [ | 
|  | ///        ["input1.dart", "green", "blue"] | 
|  | ///      ] | 
|  | ///    } | 
|  | ///  ] | 
|  | /// | 
|  | ///  Is interpreted as 2 iterations, the first iteration updates input1.dart | 
|  | ///  with 2 changes, and input2.dart with one change. The second iteration | 
|  | ///  updates input1.dart a second time. | 
|  | library front_end.tool.incremental_perf; | 
|  |  | 
|  | import 'dart:convert'; | 
|  | import 'dart:io' hide FileSystemEntity; | 
|  | import 'dart:typed_data'; | 
|  |  | 
|  | import 'package:args/args.dart'; | 
|  | import 'package:front_end/src/api_prototype/front_end.dart'; | 
|  | import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart'; | 
|  | import 'package:front_end/src/api_prototype/memory_file_system.dart'; | 
|  | import 'package:front_end/src/api_prototype/standard_file_system.dart'; | 
|  | import 'package:front_end/src/base/processed_options.dart'; | 
|  | import 'package:front_end/src/base/uri_translator.dart'; | 
|  |  | 
|  | import 'perf_common.dart'; | 
|  |  | 
|  | Future<void> main(List<String> args) async { | 
|  | var options = argParser.parse(args); | 
|  | if (options.rest.length != 2) { | 
|  | throw """ | 
|  | usage: incremental_perf.dart [options] <entry.dart> <edits.json> | 
|  | ${argParser.usage}"""; | 
|  | } | 
|  |  | 
|  | var entryUri = _resolveOverlayUri(options.rest[0]); | 
|  | var editsUri = Uri.base.resolve(options.rest[1]); | 
|  | var changeSets = | 
|  | parse(jsonDecode(new File.fromUri(editsUri).readAsStringSync())); | 
|  | bool verbose = options["verbose"]; | 
|  | bool verboseCompilation = options["verbose-compilation"]; | 
|  | bool isFlutter = options["target"] == "flutter"; | 
|  | bool useMinimalGenerator = options["implementation"] == "minimal"; | 
|  | TimingsCollector collector = new TimingsCollector(verbose); | 
|  |  | 
|  | for (int i = 0; i < 8; i++) { | 
|  | await benchmark( | 
|  | collector, | 
|  | entryUri, | 
|  | isFlutter, | 
|  | useMinimalGenerator, | 
|  | verbose, | 
|  | verboseCompilation, | 
|  | changeSets, | 
|  | options["sdk-summary"], | 
|  | options["sdk-library-specification"], | 
|  | options["cache"]); | 
|  | if (!options["loop"]) break; | 
|  | } | 
|  | collector.printTimings(); | 
|  | } | 
|  |  | 
|  | Future benchmark( | 
|  | TimingsCollector collector, | 
|  | Uri entryUri, | 
|  | bool isFlutter, | 
|  | bool useMinimalGenerator, | 
|  | bool verbose, | 
|  | bool verboseCompilation, | 
|  | List<ChangeSet> changeSets, | 
|  | String? sdkSummary, | 
|  | String? sdkLibrarySpecification, | 
|  | String cache) async { | 
|  | var overlayFs = new OverlayFileSystem(); | 
|  | var compilerOptions = new CompilerOptions() | 
|  | ..verbose = verboseCompilation | 
|  | ..fileSystem = overlayFs | 
|  | ..onDiagnostic = onDiagnosticMessageHandler() | 
|  | ..target = createTarget(isFlutter: isFlutter) | 
|  | ..environmentDefines = const {}; | 
|  | if (sdkSummary != null) { | 
|  | compilerOptions.sdkSummary = _resolveOverlayUri(sdkSummary); | 
|  | } | 
|  | if (sdkLibrarySpecification != null) { | 
|  | compilerOptions.librariesSpecificationUri = | 
|  | _resolveOverlayUri(sdkLibrarySpecification); | 
|  | } | 
|  |  | 
|  | var dir = Directory.systemTemp.createTempSync("ikg-cache"); | 
|  |  | 
|  | final processedOptions = | 
|  | new ProcessedOptions(options: compilerOptions, inputs: [entryUri]); | 
|  | final UriTranslator uriTranslator = await processedOptions.getUriTranslator(); | 
|  |  | 
|  | collector.start("Initial compilation"); | 
|  | var generator = new IncrementalKernelGenerator(compilerOptions, [entryUri]); | 
|  |  | 
|  | var compilerResult = await generator.computeDelta(); | 
|  | var component = compilerResult.component; | 
|  | collector.stop("Initial compilation"); | 
|  | if (verbose) { | 
|  | print("Libraries changed: ${component.libraries.length}"); | 
|  | } | 
|  | if (component.libraries.length < 1) { | 
|  | throw "No libraries were changed"; | 
|  | } | 
|  |  | 
|  | for (final ChangeSet changeSet in changeSets) { | 
|  | String name = "Change '${changeSet.name}' - Incremental compilation"; | 
|  | await applyEdits( | 
|  | changeSet.edits, overlayFs, generator, uriTranslator, verbose); | 
|  | collector.start(name); | 
|  | compilerResult = await generator.computeDelta(); | 
|  | component = compilerResult.component; | 
|  | collector.stop(name); | 
|  | if (verbose) { | 
|  | print("Change '${changeSet.name}' - " | 
|  | "Libraries changed: ${component.libraries.length}"); | 
|  | } | 
|  | if (component.libraries.length < 1) { | 
|  | throw "No libraries were changed"; | 
|  | } | 
|  | } | 
|  |  | 
|  | dir.deleteSync(recursive: true); | 
|  | } | 
|  |  | 
|  | /// Apply all edits of a single iteration by updating the copy of the file in | 
|  | /// the memory file system. | 
|  | Future<void> applyEdits( | 
|  | List<Edit> edits, | 
|  | OverlayFileSystem fs, | 
|  | IncrementalKernelGenerator generator, | 
|  | UriTranslator uriTranslator, | 
|  | bool verbose) async { | 
|  | for (var edit in edits) { | 
|  | if (verbose) { | 
|  | print('edit $edit'); | 
|  | } | 
|  | var uri = edit.uri; | 
|  | if (uri.isScheme('package')) uri = uriTranslator.translate(uri)!; | 
|  | generator.invalidate(uri); | 
|  | OverlayFileSystemEntity entity = | 
|  | fs.entityForUri(uri) as OverlayFileSystemEntity; | 
|  | var contents = await entity.readAsString(); | 
|  | entity.writeAsStringSync( | 
|  | contents.replaceAll(edit.original, edit.replacement)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Parse a set of edits from a JSON array. See library comment above for | 
|  | /// details on the format. | 
|  | List<ChangeSet> parse(List json) { | 
|  | final changeSets = <ChangeSet>[]; | 
|  | for (final Map jsonChangeSet in json) { | 
|  | final edits = <Edit>[]; | 
|  | for (final jsonEdit in jsonChangeSet['edits']) { | 
|  | edits.add(new Edit(jsonEdit[0], jsonEdit[1], jsonEdit[2])); | 
|  | } | 
|  | changeSets.add(new ChangeSet(jsonChangeSet['name'], edits)); | 
|  | } | 
|  | return changeSets; | 
|  | } | 
|  |  | 
|  | /// An overlay file system that reads the original contents from the physical | 
|  | /// file system, but performs updates to those files in memory. | 
|  | /// | 
|  | /// All files in this file system use a custom URI of the form: | 
|  | /// | 
|  | ///   org-dartlang-overlay:///path/to/file.dart | 
|  | /// | 
|  | /// This special scheme is mainly used to make it clear that the file belongs to | 
|  | /// this file system and may not correspond to the contents on disk. However, | 
|  | /// when the file is read for the first time, it will be retrieved from the | 
|  | /// underlying file system by using the corresponding `file:*` URI: | 
|  | /// | 
|  | ///   file:///path/to/file.dart | 
|  | class OverlayFileSystem implements FileSystem { | 
|  | final MemoryFileSystem memory = | 
|  | new MemoryFileSystem(Uri.parse('org-dartlang-overlay:///')); | 
|  | final StandardFileSystem physical = StandardFileSystem.instance; | 
|  |  | 
|  | @override | 
|  | FileSystemEntity entityForUri(Uri uri) { | 
|  | if (uri.isScheme('org-dartlang-overlay')) { | 
|  | return new OverlayFileSystemEntity(uri, this); | 
|  | } else if (uri.isScheme('file')) { | 
|  | // The IKG compiler reads ".dart_tool/package_config.json" which might | 
|  | // contain absolute file URIs (which it will then try to use on the FS). | 
|  | // We therefore replace them with overlay-fs URIs as usual. | 
|  | return new OverlayFileSystemEntity(_resolveOverlayUri('$uri'), this); | 
|  | } else { | 
|  | throw "Unsupported scheme: ${uri.scheme}." | 
|  | " The OverlayFileSystem only accepts URIs" | 
|  | " with the 'org-dartlang-overlay' scheme"; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class OverlayFileSystemEntity implements FileSystemEntity { | 
|  | @override | 
|  | final Uri uri; | 
|  | FileSystemEntity? _delegate; | 
|  | final OverlayFileSystem _fs; | 
|  |  | 
|  | OverlayFileSystemEntity(this.uri, this._fs); | 
|  |  | 
|  | Future<FileSystemEntity> get delegate async { | 
|  | if (_delegate != null) return _delegate!; | 
|  | FileSystemEntity entity = _fs.memory.entityForUri(uri); | 
|  | if (await entity.exists()) { | 
|  | _delegate = entity; | 
|  | return _delegate!; | 
|  | } | 
|  | return _delegate = _fs.physical.entityForUri(uri.replace(scheme: 'file')); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Future<bool> exists() async => (await delegate).exists(); | 
|  |  | 
|  | @override | 
|  | Future<bool> existsAsyncIfPossible() async => | 
|  | (await delegate).existsAsyncIfPossible(); | 
|  |  | 
|  | @override | 
|  | Future<Uint8List> readAsBytes() async => (await delegate).readAsBytes(); | 
|  |  | 
|  | @override | 
|  | Future<Uint8List> readAsBytesAsyncIfPossible() async => | 
|  | (await delegate).readAsBytesAsyncIfPossible(); | 
|  |  | 
|  | @override | 
|  | Future<String> readAsString() async => (await delegate).readAsString(); | 
|  |  | 
|  | void writeAsStringSync(String contents) => | 
|  | _fs.memory.entityForUri(uri).writeAsStringSync(contents); | 
|  | } | 
|  |  | 
|  | /// A string replacement edit in a source file. | 
|  | class Edit { | 
|  | final Uri uri; | 
|  | final String original; | 
|  | final String replacement; | 
|  |  | 
|  | Edit(String uriString, this.original, this.replacement) | 
|  | : uri = _resolveOverlayUri(uriString); | 
|  |  | 
|  | @override | 
|  | String toString() => 'Edit($uri, "$original" -> "$replacement")'; | 
|  | } | 
|  |  | 
|  | /// A named set of changes applied together. | 
|  | class ChangeSet { | 
|  | final String name; | 
|  | final List<Edit> edits; | 
|  |  | 
|  | ChangeSet(this.name, this.edits); | 
|  |  | 
|  | @override | 
|  | String toString() => 'ChangeSet($name, $edits)'; | 
|  | } | 
|  |  | 
|  | Uri _resolveOverlayUri(String uriString) { | 
|  | Uri result = Uri.base.resolve(uriString); | 
|  | return result.isScheme("file") | 
|  | ? result.replace(scheme: 'org-dartlang-overlay') | 
|  | : result; | 
|  | } | 
|  |  | 
|  | ArgParser argParser = new ArgParser() | 
|  | ..addFlag('verbose-compilation', | 
|  | help: 'make the compiler verbose', defaultsTo: false) | 
|  | ..addFlag('verbose', help: 'print additional information', defaultsTo: false) | 
|  | ..addFlag('loop', help: 'run benchmark 8 times', defaultsTo: true) | 
|  | ..addOption('target', | 
|  | help: 'target platform', defaultsTo: 'vm', allowed: ['vm', 'flutter']) | 
|  | ..addOption('cache', | 
|  | help: 'caching policy used by the compiler', | 
|  | defaultsTo: 'protected', | 
|  | allowed: ['evicting', 'memory', 'protected']) | 
|  | // TODO(johnniwinther): Remove mode option. Legacy mode is no longer | 
|  | // supported. | 
|  | ..addOption('mode', | 
|  | help: 'whether to run in strong or legacy mode', | 
|  | defaultsTo: 'strong', | 
|  | allowed: ['legacy', 'strong']) | 
|  | ..addOption('implementation', | 
|  | help: 'incremental compiler implementation to use', | 
|  | defaultsTo: 'default', | 
|  | allowed: ['default', 'minimal']) | 
|  | ..addOption('sdk-summary', help: 'Location of the sdk outline.dill file') | 
|  | ..addOption('sdk-library-specification', | 
|  | help: 'Location of the ' | 
|  | 'sdk/lib/libraries.json file'); |