9b44fc39c8
The option is not used by any client at this time and removing it simplifies the next stack of changes I'll be making on how we compose file systems. I also don't believe we will necessarily add this back as an option, but instead it might be that clients requriing hermeticity will provide a custom file system that helps with that. Change-Id: I401efb042920d234382b6c041b13d40ffae5c908 Reviewed-on: https://dart-review.googlesource.com/56462 Commit-Queue: Peter von der Ahé <ahe@google.com> Reviewed-by: Peter von der Ahé <ahe@google.com>
332 lines
11 KiB
Dart
332 lines
11 KiB
Dart
// 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_chnage",
|
|
/// "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:async';
|
|
import 'dart:convert';
|
|
import 'dart:io' hide FileSystemEntity;
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:front_end/src/api_prototype/byte_store.dart';
|
|
import 'package:front_end/src/api_prototype/file_system.dart'
|
|
show FileSystemEntity;
|
|
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/byte_store/protected_file_byte_store.dart';
|
|
import 'package:front_end/src/fasta/uri_translator.dart';
|
|
|
|
import 'perf_common.dart';
|
|
|
|
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 strongMode = options["mode"] == "strong";
|
|
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,
|
|
strongMode,
|
|
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 strongMode,
|
|
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
|
|
..strongMode = strongMode
|
|
..onError = onErrorHandler(strongMode)
|
|
..target = createTarget(isFlutter: isFlutter, strongMode: strongMode);
|
|
if (sdkSummary != null) {
|
|
compilerOptions.sdkSummary = _resolveOverlayUri(sdkSummary);
|
|
}
|
|
if (sdkLibrarySpecification != null) {
|
|
compilerOptions.librariesSpecificationUri =
|
|
_resolveOverlayUri(sdkLibrarySpecification);
|
|
}
|
|
|
|
var dir = Directory.systemTemp.createTempSync("ikg-cache");
|
|
compilerOptions.byteStore = createByteStore(cache, dir.path);
|
|
|
|
final processedOptions = new ProcessedOptions(compilerOptions, [entryUri]);
|
|
final UriTranslator uriTranslator = await processedOptions.getUriTranslator();
|
|
|
|
collector.start("Initial compilation");
|
|
var generator = new IncrementalKernelGenerator(compilerOptions, entryUri);
|
|
|
|
var component = await generator.computeDelta();
|
|
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);
|
|
component = await generator.computeDelta();
|
|
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.
|
|
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.scheme == 'package') uri = uriTranslator.translate(uri);
|
|
generator.invalidate(uri);
|
|
OverlayFileSystemEntity entity = fs.entityForUri(uri);
|
|
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.scheme == 'org-dartlang-overlay') {
|
|
return new OverlayFileSystemEntity(uri, this);
|
|
} else if (uri.scheme == 'file') {
|
|
// The IKG compiler reads ".packages" 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 {
|
|
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<List<int>> readAsBytes() async => (await delegate).readAsBytes();
|
|
|
|
@override
|
|
Future<String> readAsString() async => (await delegate).readAsString();
|
|
|
|
void writeAsStringSync(String contents) =>
|
|
_fs.memory.entityForUri(uri).writeAsStringSync(contents);
|
|
}
|
|
|
|
ByteStore createByteStore(String cachePolicy, String path) {
|
|
switch (cachePolicy) {
|
|
case 'memory':
|
|
return new MemoryByteStore();
|
|
case 'protected':
|
|
return new ProtectedFileByteStore(path);
|
|
case 'evicting':
|
|
return new MemoryCachingByteStore(
|
|
new EvictingFileByteStore(path, 1024 * 1024 * 1024 /* 1G */),
|
|
64 * 1024 * 1024 /* 64M */);
|
|
default:
|
|
throw new UnsupportedError('Unknown cache policy: $cachePolicy');
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
|
|
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);
|
|
|
|
String toString() => 'ChangeSet($name, $edits)';
|
|
}
|
|
|
|
_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'])
|
|
..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');
|