| // 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_test/support/integration_test_methods.dart'; | |
| import '../../integration_test/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. | |
| new(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 const int _defaultWarmupCount = 10; | |
| /// The number of times the test will be performed in order to compute a time. | |
| static const int _defaultTimingCount = 10; | |
| /// The amount of time to give the server to respond to a shutdown request | |
| /// before forcibly terminating it. | |
| static const Duration _shutdownTimeout = 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 => _defaultTimingCount; | |
| /// Return the number of iterations that should be performed in order to warm | |
| /// up the VM. | |
| int get warmupCount => _defaultWarmupCount; | |
| /// Perform any operations that need to be performed once before any | |
| /// iterations. | |
| Future<void> oneTimeSetUp() { | |
| server = Server(); | |
| sourceDirectory = Directory.systemTemp.createTempSync( | |
| 'analysisServer_test_timing', | |
| ); | |
| 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( | |
| _shutdownTimeout, | |
| onTimeout: () { | |
| return server.kill('server failed to exit'); | |
| }, | |
| ); | |
| } | |
| } |