313 lines
8.3 KiB
Dart
313 lines
8.3 KiB
Dart
// Copyright (c) 2014, 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.
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
|
|
import 'package:path/path.dart';
|
|
|
|
import '../integration/support/integration_test_methods.dart';
|
|
import '../integration/support/integration_tests.dart';
|
|
|
|
/**
|
|
* Instances of the class [TimingResult] represent the timing information
|
|
* gathered while executing a given timing test.
|
|
*/
|
|
class TimingResult {
|
|
/**
|
|
* The number of nanoseconds in a millisecond.
|
|
*/
|
|
static int NANOSECONDS_PER_MILLISECOND = 1000000;
|
|
|
|
/**
|
|
* The amount of time spent executing each test, in nanoseconds.
|
|
*/
|
|
List<int> times;
|
|
|
|
/**
|
|
* Initialize a newly created timing result.
|
|
*/
|
|
TimingResult(this.times);
|
|
|
|
/**
|
|
* The average amount of time spent executing a single iteration, in
|
|
* milliseconds.
|
|
*/
|
|
int get averageTime {
|
|
return totalTime ~/ times.length;
|
|
}
|
|
|
|
/**
|
|
* The maximum amount of time spent executing a single iteration, in
|
|
* milliseconds.
|
|
*/
|
|
int get maxTime {
|
|
int maxTime = 0;
|
|
int count = times.length;
|
|
for (int i = 0; i < count; i++) {
|
|
maxTime = max(maxTime, times[i]);
|
|
}
|
|
return maxTime ~/ NANOSECONDS_PER_MILLISECOND;
|
|
}
|
|
|
|
/**
|
|
* The minimum amount of time spent executing a single iteration, in
|
|
* milliseconds.
|
|
*/
|
|
int get minTime {
|
|
int minTime = times[0];
|
|
int count = times.length;
|
|
for (int i = 1; i < count; i++) {
|
|
minTime = min(minTime, times[i]);
|
|
}
|
|
return minTime ~/ NANOSECONDS_PER_MILLISECOND;
|
|
}
|
|
|
|
/**
|
|
* The standard deviation of the times.
|
|
*/
|
|
double get standardDeviation {
|
|
return computeStandardDeviation(toMilliseconds(times));
|
|
}
|
|
|
|
/**
|
|
* The total amount of time spent executing the test, in milliseconds.
|
|
*/
|
|
int get totalTime {
|
|
int totalTime = 0;
|
|
int count = times.length;
|
|
for (int i = 0; i < count; i++) {
|
|
totalTime += times[i];
|
|
}
|
|
return totalTime ~/ NANOSECONDS_PER_MILLISECOND;
|
|
}
|
|
|
|
/**
|
|
* Compute the standard deviation of the given set of [values].
|
|
*/
|
|
double computeStandardDeviation(List<int> values) {
|
|
int count = values.length;
|
|
double sumOfValues = 0.0;
|
|
for (int i = 0; i < count; i++) {
|
|
sumOfValues += values[i];
|
|
}
|
|
double average = sumOfValues / count;
|
|
double sumOfDiffSquared = 0.0;
|
|
for (int i = 0; i < count; i++) {
|
|
double diff = values[i] - average;
|
|
sumOfDiffSquared += diff * diff;
|
|
}
|
|
return sqrt((sumOfDiffSquared / (count - 1)));
|
|
}
|
|
|
|
/**
|
|
* Convert the given [times], expressed in nanoseconds, to times expressed in
|
|
* milliseconds.
|
|
*/
|
|
List<int> toMilliseconds(List<int> times) {
|
|
int count = times.length;
|
|
List<int> convertedValues = new List<int>();
|
|
for (int i = 0; i < count; i++) {
|
|
convertedValues.add(times[i] ~/ NANOSECONDS_PER_MILLISECOND);
|
|
}
|
|
return convertedValues;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The abstract class [TimingTest] defines the behavior of objects that measure
|
|
* the time required to perform some sequence of server operations.
|
|
*/
|
|
abstract class TimingTest extends IntegrationTestMixin {
|
|
/**
|
|
* The number of times the test will be performed in order to warm up the VM.
|
|
*/
|
|
static final int DEFAULT_WARMUP_COUNT = 10;
|
|
|
|
/**
|
|
* The number of times the test will be performed in order to compute a time.
|
|
*/
|
|
static final int DEFAULT_TIMING_COUNT = 10;
|
|
|
|
/**
|
|
* The file suffix used to identify Dart files.
|
|
*/
|
|
static final String DART_SUFFIX = '.dart';
|
|
|
|
/**
|
|
* The file suffix used to identify HTML files.
|
|
*/
|
|
static final String HTML_SUFFIX = '.html';
|
|
|
|
/**
|
|
* The amount of time to give the server to respond to a shutdown request
|
|
* before forcibly terminating it.
|
|
*/
|
|
static const Duration SHUTDOWN_TIMEOUT = const Duration(seconds: 5);
|
|
|
|
/**
|
|
* The connection to the analysis server.
|
|
*/
|
|
Server server;
|
|
|
|
/**
|
|
* The temporary directory in which source files can be stored.
|
|
*/
|
|
Directory sourceDirectory;
|
|
|
|
/**
|
|
* A flag indicating whether the teardown process should skip sending a
|
|
* "server.shutdown" request because the server is known to have already
|
|
* shutdown.
|
|
*/
|
|
bool skipShutdown = false;
|
|
|
|
/**
|
|
* Initialize a newly created test.
|
|
*/
|
|
TimingTest();
|
|
|
|
/**
|
|
* Return the number of iterations that should be performed in order to
|
|
* compute a time.
|
|
*/
|
|
int get timingCount => DEFAULT_TIMING_COUNT;
|
|
|
|
/**
|
|
* Return the number of iterations that should be performed in order to warm
|
|
* up the VM.
|
|
*/
|
|
int get warmupCount => DEFAULT_WARMUP_COUNT;
|
|
|
|
/**
|
|
* Perform any operations that need to be performed once before any iterations.
|
|
*/
|
|
Future oneTimeSetUp() {
|
|
initializeInttestMixin();
|
|
server = new Server();
|
|
sourceDirectory = Directory.systemTemp.createTempSync('analysisServer');
|
|
Completer serverConnected = new Completer();
|
|
onServerConnected.listen((_) {
|
|
serverConnected.complete();
|
|
});
|
|
skipShutdown = true;
|
|
return server.start(/*profileServer: true*/).then((_) {
|
|
server.listenToOutput(dispatchNotification);
|
|
server.exitCode.then((_) {
|
|
skipShutdown = true;
|
|
});
|
|
return serverConnected.future;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Perform any operations that need to be performed once after all iterations.
|
|
*/
|
|
Future oneTimeTearDown() {
|
|
return _shutdownIfNeeded().then((_) {
|
|
sourceDirectory.deleteSync(recursive: true);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Perform any operations that part of a single iteration. It is the execution
|
|
* of this method that will be measured.
|
|
*/
|
|
Future perform();
|
|
|
|
/**
|
|
* Return a future that will complete with a timing result representing the
|
|
* number of milliseconds required to perform the operation the specified
|
|
* number of times.
|
|
*/
|
|
Future<TimingResult> run() async {
|
|
List<int> times = new List<int>();
|
|
await oneTimeSetUp();
|
|
await _repeat(warmupCount, null);
|
|
await _repeat(timingCount, times);
|
|
await oneTimeTearDown();
|
|
return new Future<TimingResult>.value(new TimingResult(times));
|
|
}
|
|
|
|
/**
|
|
* Perform any operations that need to be performed before each iteration.
|
|
*/
|
|
Future setUp();
|
|
|
|
/**
|
|
* Convert the given [relativePath] to an absolute path, by interpreting it
|
|
* relative to [sourceDirectory]. On Windows any forward slashes in
|
|
* [relativePath] are converted to backslashes.
|
|
*/
|
|
String sourcePath(String relativePath) {
|
|
return join(sourceDirectory.path, relativePath.replaceAll('/', separator));
|
|
}
|
|
|
|
/**
|
|
* Perform any operations that need to be performed after each iteration.
|
|
*/
|
|
Future tearDown();
|
|
|
|
/**
|
|
* Write a source file with the given absolute [pathname] and [contents].
|
|
*
|
|
* If the file didn't previously exist, it is created. If it did, it is
|
|
* overwritten.
|
|
*
|
|
* Parent directories are created as necessary.
|
|
*/
|
|
void writeFile(String pathname, String contents) {
|
|
new Directory(dirname(pathname)).createSync(recursive: true);
|
|
new File(pathname).writeAsStringSync(contents);
|
|
}
|
|
|
|
/**
|
|
* Return the number of nanoseconds that have elapsed since the given
|
|
* [stopwatch] was last stopped.
|
|
*/
|
|
int _elapsedNanoseconds(Stopwatch stopwatch) {
|
|
return (stopwatch.elapsedTicks * 1000000000) ~/ stopwatch.frequency;
|
|
}
|
|
|
|
/**
|
|
* Repeatedly execute this test [count] times, adding timing information to
|
|
* the given list of [times] if it is non-`null`.
|
|
*/
|
|
Future _repeat(int count, List<int> times) {
|
|
Stopwatch stopwatch = new Stopwatch();
|
|
return setUp().then((_) {
|
|
stopwatch.start();
|
|
return perform().then((_) {
|
|
stopwatch.stop();
|
|
if (times != null) {
|
|
times.add(_elapsedNanoseconds(stopwatch));
|
|
}
|
|
return tearDown().then((_) {
|
|
if (count > 0) {
|
|
return _repeat(count - 1, times);
|
|
} else {
|
|
return new Future.value();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Shut the server down unless [skipShutdown] is `true`.
|
|
*/
|
|
Future _shutdownIfNeeded() {
|
|
if (skipShutdown) {
|
|
return new Future.value();
|
|
}
|
|
// Give the server a short time to comply with the shutdown request; if it
|
|
// doesn't exit, then forcibly terminate it.
|
|
sendServerShutdown();
|
|
return server.exitCode.timeout(SHUTDOWN_TIMEOUT, onTimeout: () {
|
|
return server.kill('server failed to exit');
|
|
});
|
|
}
|
|
}
|