| // 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 { |
| var maxTime = 0; |
| var count = times.length; |
| for (var 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 { |
| var minTime = times[0]; |
| var count = times.length; |
| for (var 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 { |
| var totalTime = 0; |
| var count = times.length; |
| for (var 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) { |
| var count = values.length; |
| var sumOfValues = 0; |
| for (var i = 0; i < count; i++) { |
| sumOfValues += values[i]; |
| } |
| var average = sumOfValues / count; |
| var sumOfDiffSquared = 0.0; |
| for (var i = 0; i < count; i++) { |
| var 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) { |
| var count = times.length; |
| var convertedValues = <int>[]; |
| for (var 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 IntegrationTest { |
| /// 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 = Duration(seconds: 5); |
| |
| /// The connection to the analysis server. |
| @override |
| late Server server; |
| |
| /// The temporary directory in which source files can be stored. |
| late 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; |
| |
| /// 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<void> oneTimeSetUp() { |
| server = Server(); |
| sourceDirectory = Directory.systemTemp.createTempSync('analysisServer'); |
| var serverConnected = Completer<void>(); |
| onServerConnected.listen((_) { |
| serverConnected.complete(); |
| }); |
| skipShutdown = true; |
| var dartSdkPath = dirname(dirname(Platform.resolvedExecutable)); |
| return server.start(dartSdkPath: dartSdkPath).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<void> 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<void> 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 { |
| var times = <int>[]; |
| await oneTimeSetUp(); |
| await _repeat(warmupCount, null); |
| await _repeat(timingCount, times); |
| await oneTimeTearDown(); |
| return TimingResult(times); |
| } |
| |
| /// Perform any operations that need to be performed before each iteration. |
| Future<void> 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<void> 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) { |
| Directory(dirname(pathname)).createSync(recursive: true); |
| 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<void> _repeat(int count, List<int>? times) { |
| var stopwatch = 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 Future.value(); |
| } |
| }); |
| }); |
| }); |
| } |
| |
| /// Shut the server down unless [skipShutdown] is `true`. |
| Future<void> _shutdownIfNeeded() { |
| if (skipShutdown) { |
| return 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'); |
| }); |
| } |
| } |