Files
sdk/pkg/front_end/tool/perf.dart
T
Paul Berry 9a82ada085 Remove hardcoded paths from perf.dart.
This lets perf.dart find the SDK and the .packages file regardless of
the architecture that's being tested and the working directory in
which the script is started.

Fixes #28676.

R=sigmund@google.com

Review-Url: https://codereview.chromium.org/2687723002 .
2017-02-08 16:48:20 -08:00

373 lines
12 KiB
Dart

// 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 front_end and measure its performance.
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/file_system.dart' show ResourceUriResolver;
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/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/compiler_options.dart';
import 'package:front_end/kernel_generator.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:kernel/kernel.dart' hide Source;
import 'package:package_config/discovery.dart';
import 'package:path/path.dart' as path;
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]);
await setup(entryUri);
Set<Source> files = scanReachableFiles(entryUri);
var handlers = {
'scan': () async => scanFiles(files),
'parse': () async => parseFiles(files),
'kernel_gen_e2e': () async {
await generateKernel(entryUri, useSdkSummary: false);
},
'kernel_gen_e2e_sum': () async {
await generateKernel(entryUri, useSdkSummary: true, compileSdk: false);
},
'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;
/// File URI of the root of the SDK source tree.
final _repoUri = Platform.script.resolve('../../../');
/// Path to the root of the built SDK that is being used to execute this script.
final _sdkPath = _findSdkPath();
/// File URI to the root of the built SDK that is being used to execute this
/// script.
final _sdkUri = new Uri.directory(_sdkPath);
/// 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);
}
}
}
Future<Program> generateKernel(Uri entryUri,
{bool useSdkSummary: false, bool compileSdk: true}) async {
// TODO(sigmund): this is here only to compute the input size,
// we should extract the input size from the frontend instead.
scanReachableFiles(entryUri);
var dartkTimer = new Stopwatch()..start();
// TODO(sigmund): add a constructor with named args to compiler options.
var options = new CompilerOptions()
..strongMode = false
..compileSdk = compileSdk
..packagesFileUri = _repoUri.resolve('.packages')
..onError = ((e) => print('${e.message}'));
if (useSdkSummary) {
options.sdkSummary = _sdkUri.resolve('lib/_internal/spec.sum');
} else {
options.sdkRoot = _sdkUri;
}
Program program = await kernelForProgram(entryUri, options);
dartkTimer.stop();
var suffix = useSdkSummary ? '_sum' : '';
report('kernel_gen_e2e${suffix}', dartkTimer.elapsedMicroseconds);
return program;
}
/// Generates unlinkmed summaries for all files in [files], and returns them in
/// an [_UnlinkedSummaries] container.
_UnlinkedSummaries generateUnlinkedSummaries(Set<Source> 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<Source> 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<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 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<String, LinkedLibraryBuilder> prelinkSummaries(
Set<Source> files, _UnlinkedSummaries unlinkedSummaries) {
Set<String> 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<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.
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 = <String, UnlinkedUnit>{};
/// 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(new CharSequenceReader(contents)) {
preserveComments = false;
}
@override
void reportError(errorCode, int offset, List<Object> arguments) {
// ignore errors.
}
}