| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library server.driver; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:math' show max, sqrt; | |
| 9 | |
| 10 import 'package:analyzer/src/generated/engine.dart' as engine; | |
| 11 import 'package:logging/logging.dart'; | |
| 12 | |
| 13 import '../integration/integration_test_methods.dart'; | |
| 14 import '../integration/integration_tests.dart'; | |
| 15 import 'operation.dart'; | |
| 16 | |
| 17 final SPACE = ' '.codeUnitAt(0); | |
| 18 | |
| 19 void _printColumn(StringBuffer sb, String text, int keyLen, | |
| 20 {bool rightJustified: false}) { | |
| 21 if (!rightJustified) { | |
| 22 sb.write(text); | |
| 23 sb.write(','); | |
| 24 } | |
| 25 for (int i = text.length; i < keyLen; ++i) { | |
| 26 sb.writeCharCode(SPACE); | |
| 27 } | |
| 28 if (rightJustified) { | |
| 29 sb.write(text); | |
| 30 sb.write(','); | |
| 31 } | |
| 32 sb.writeCharCode(SPACE); | |
| 33 } | |
| 34 | |
| 35 /** | |
| 36 * [Driver] launches and manages an instance of analysis server, | |
| 37 * reads a stream of operations, sends requests to analysis server | |
| 38 * based upon those operations, and evaluates the results. | |
| 39 */ | |
| 40 class Driver extends IntegrationTestMixin { | |
| 41 /** | |
| 42 * The amount of time to give the server to respond to a shutdown request | |
| 43 * before forcibly terminating it. | |
| 44 */ | |
| 45 static const Duration SHUTDOWN_TIMEOUT = const Duration(seconds: 5); | |
| 46 | |
| 47 final Logger logger; | |
| 48 | |
| 49 /** | |
| 50 * A flag indicating whether the server is running. | |
| 51 */ | |
| 52 bool running = false; | |
| 53 | |
| 54 @override | |
| 55 Server server; | |
| 56 | |
| 57 /** | |
| 58 * The results collected while running analysis server. | |
| 59 */ | |
| 60 final Results results = new Results(); | |
| 61 | |
| 62 /** | |
| 63 * The [Completer] for [runComplete]. | |
| 64 */ | |
| 65 Completer<Results> _runCompleter = new Completer<Results>(); | |
| 66 | |
| 67 Driver(this.logger); | |
| 68 | |
| 69 /** | |
| 70 * Return a [Future] that completes with the [Results] of running | |
| 71 * the analysis server once all operations have been performed. | |
| 72 */ | |
| 73 Future<Results> get runComplete => _runCompleter.future; | |
| 74 | |
| 75 /** | |
| 76 * Perform the given operation. | |
| 77 * Return a [Future] that completes when the next operation can be performed, | |
| 78 * or `null` if the next operation can be performed immediately | |
| 79 */ | |
| 80 Future perform(Operation op) { | |
| 81 return op.perform(this); | |
| 82 } | |
| 83 | |
| 84 /** | |
| 85 * Send a command to the server. An 'id' will be automatically assigned. | |
| 86 * The returned [Future] will be completed when the server acknowledges the | |
| 87 * command with a response. If the server acknowledges the command with a | |
| 88 * normal (non-error) response, the future will be completed with the 'result' | |
| 89 * field from the response. If the server acknowledges the command with an | |
| 90 * error response, the future will be completed with an error. | |
| 91 */ | |
| 92 Future send(String method, Map<String, dynamic> params) { | |
| 93 return server.send(method, params); | |
| 94 } | |
| 95 | |
| 96 /** | |
| 97 * Launch the analysis server. | |
| 98 * Return a [Future] that completes when analysis server has started. | |
| 99 */ | |
| 100 Future startServer({int diagnosticPort}) async { | |
| 101 logger.log(Level.FINE, 'starting server'); | |
| 102 initializeInttestMixin(); | |
| 103 server = new Server(); | |
| 104 Completer serverConnected = new Completer(); | |
| 105 onServerConnected.listen((_) { | |
| 106 logger.log(Level.FINE, 'connected to server'); | |
| 107 serverConnected.complete(); | |
| 108 }); | |
| 109 running = true; | |
| 110 return server | |
| 111 .start(diagnosticPort: diagnosticPort /*profileServer: true*/) | |
| 112 .then((params) { | |
| 113 server.listenToOutput(dispatchNotification); | |
| 114 server.exitCode.then((_) { | |
| 115 logger.log(Level.FINE, 'server stopped'); | |
| 116 running = false; | |
| 117 _resultsReady(); | |
| 118 }); | |
| 119 return serverConnected.future; | |
| 120 }); | |
| 121 } | |
| 122 | |
| 123 /** | |
| 124 * Shutdown the analysis server if it is running. | |
| 125 */ | |
| 126 Future stopServer([Duration timeout = SHUTDOWN_TIMEOUT]) async { | |
| 127 if (running) { | |
| 128 logger.log(Level.FINE, 'requesting server shutdown'); | |
| 129 // Give the server a short time to comply with the shutdown request; if it | |
| 130 // doesn't exit, then forcibly terminate it. | |
| 131 sendServerShutdown(); | |
| 132 await server.exitCode.timeout(timeout, onTimeout: () { | |
| 133 return server.kill(); | |
| 134 }); | |
| 135 } | |
| 136 _resultsReady(); | |
| 137 } | |
| 138 | |
| 139 /** | |
| 140 * If not already complete, signal the completer with the collected results. | |
| 141 */ | |
| 142 void _resultsReady() { | |
| 143 if (!_runCompleter.isCompleted) { | |
| 144 _runCompleter.complete(results); | |
| 145 } | |
| 146 } | |
| 147 } | |
| 148 | |
| 149 /** | |
| 150 * [Measurement] tracks elapsed time for a given operation. | |
| 151 */ | |
| 152 class Measurement { | |
| 153 final String tag; | |
| 154 final bool notification; | |
| 155 final List<Duration> elapsedTimes = new List<Duration>(); | |
| 156 int errorCount = 0; | |
| 157 int unexpectedResultCount = 0; | |
| 158 | |
| 159 Measurement(this.tag, this.notification); | |
| 160 | |
| 161 int get count => elapsedTimes.length; | |
| 162 | |
| 163 void printSummary(int keyLen) { | |
| 164 int count = 0; | |
| 165 Duration maxTime = elapsedTimes[0]; | |
| 166 Duration minTime = elapsedTimes[0]; | |
| 167 int totalTimeMicros = 0; | |
| 168 for (Duration elapsed in elapsedTimes) { | |
| 169 ++count; | |
| 170 int timeMicros = elapsed.inMicroseconds; | |
| 171 maxTime = maxTime.compareTo(elapsed) > 0 ? maxTime : elapsed; | |
| 172 minTime = minTime.compareTo(elapsed) < 0 ? minTime : elapsed; | |
| 173 totalTimeMicros += timeMicros; | |
| 174 } | |
| 175 int meanTime = (totalTimeMicros / count).round(); | |
| 176 List<Duration> sorted = elapsedTimes.toList()..sort(); | |
| 177 Duration time90th = sorted[(sorted.length * 0.90).round() - 1]; | |
| 178 Duration time99th = sorted[(sorted.length * 0.99).round() - 1]; | |
| 179 int differenceFromMeanSquared = 0; | |
| 180 for (Duration elapsed in elapsedTimes) { | |
| 181 int timeMicros = elapsed.inMicroseconds; | |
| 182 int differenceFromMean = timeMicros - meanTime; | |
| 183 differenceFromMeanSquared += differenceFromMean * differenceFromMean; | |
| 184 } | |
| 185 double variance = differenceFromMeanSquared / count; | |
| 186 int standardDeviation = sqrt(variance).round(); | |
| 187 | |
| 188 StringBuffer sb = new StringBuffer(); | |
| 189 _printColumn(sb, tag, keyLen); | |
| 190 _printColumn(sb, count.toString(), 6, rightJustified: true); | |
| 191 _printColumn(sb, errorCount.toString(), 6, rightJustified: true); | |
| 192 _printColumn(sb, unexpectedResultCount.toString(), 6, rightJustified: true); | |
| 193 _printDuration(sb, new Duration(microseconds: meanTime)); | |
| 194 _printDuration(sb, time90th); | |
| 195 _printDuration(sb, time99th); | |
| 196 _printColumn(sb, standardDeviation.toString(), 15, rightJustified: true); | |
| 197 _printDuration(sb, minTime); | |
| 198 _printDuration(sb, maxTime); | |
| 199 _printDuration(sb, new Duration(microseconds: totalTimeMicros)); | |
| 200 print(sb.toString()); | |
| 201 } | |
| 202 | |
| 203 void record(bool success, Duration elapsed) { | |
| 204 if (!success) { | |
| 205 ++errorCount; | |
| 206 } | |
| 207 elapsedTimes.add(elapsed); | |
| 208 } | |
| 209 | |
| 210 void recordUnexpectedResults() { | |
| 211 ++unexpectedResultCount; | |
| 212 } | |
| 213 | |
| 214 void _printDuration(StringBuffer sb, Duration duration) { | |
| 215 sb.write(' '); | |
| 216 sb.write(duration); | |
| 217 sb.write(','); | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 /** | |
| 222 * [Results] contains information gathered by [Driver] | |
| 223 * while running the analysis server | |
| 224 */ | |
| 225 class Results { | |
| 226 Map<String, Measurement> measurements = new Map<String, Measurement>(); | |
| 227 | |
| 228 /** | |
| 229 * Display results on stdout. | |
| 230 */ | |
| 231 void printResults() { | |
| 232 print(''); | |
| 233 print('=================================================================='); | |
| 234 if (engine.AnalysisEngine.instance.useTaskModel) { | |
| 235 print('New task model'); | |
| 236 } else { | |
| 237 print('Old task model'); | |
| 238 } | |
| 239 print(''); | |
| 240 List<String> keys = measurements.keys.toList()..sort(); | |
| 241 int keyLen = keys.fold(0, (int len, String key) => max(len, key.length)); | |
| 242 _printGroupHeader('Request/Response', keyLen); | |
| 243 int totalCount = 0; | |
| 244 int totalErrorCount = 0; | |
| 245 int totalUnexpectedResultCount = 0; | |
| 246 for (String tag in keys) { | |
| 247 Measurement m = measurements[tag]; | |
| 248 if (!m.notification) { | |
| 249 m.printSummary(keyLen); | |
| 250 totalCount += m.count; | |
| 251 totalErrorCount += m.errorCount; | |
| 252 totalUnexpectedResultCount += m.unexpectedResultCount; | |
| 253 } | |
| 254 } | |
| 255 _printTotals( | |
| 256 keyLen, totalCount, totalErrorCount, totalUnexpectedResultCount); | |
| 257 print(''); | |
| 258 _printGroupHeader('Notifications', keyLen); | |
| 259 for (String tag in keys) { | |
| 260 Measurement m = measurements[tag]; | |
| 261 if (m.notification) { | |
| 262 m.printSummary(keyLen); | |
| 263 } | |
| 264 } | |
| 265 /// TODO(danrubel) *** print warnings if driver caches are not empty **** | |
| 266 print(''); | |
| 267 print( | |
| 268 '(1) uxr = UneXpected Results, or responses received from the server'); | |
| 269 print( | |
| 270 ' that do not match the recorded response for that request.'); | |
| 271 } | |
| 272 | |
| 273 /** | |
| 274 * Record the elapsed time for the given operation. | |
| 275 */ | |
| 276 void record(String tag, Duration elapsed, | |
| 277 {bool notification: false, bool success: true}) { | |
| 278 Measurement measurement = measurements[tag]; | |
| 279 if (measurement == null) { | |
| 280 measurement = new Measurement(tag, notification); | |
| 281 measurements[tag] = measurement; | |
| 282 } | |
| 283 measurement.record(success, elapsed); | |
| 284 } | |
| 285 | |
| 286 void recordUnexpectedResults(String tag) { | |
| 287 measurements[tag].recordUnexpectedResults(); | |
| 288 } | |
| 289 | |
| 290 void _printGroupHeader(String groupName, int keyLen) { | |
| 291 StringBuffer sb = new StringBuffer(); | |
| 292 _printColumn(sb, groupName, keyLen); | |
| 293 _printColumn(sb, 'count', 6, rightJustified: true); | |
| 294 _printColumn(sb, 'error', 6, rightJustified: true); | |
| 295 _printColumn(sb, 'uxr(1)', 6, rightJustified: true); | |
| 296 sb.write(' '); | |
| 297 _printColumn(sb, 'mean', 15); | |
| 298 _printColumn(sb, '90th', 15); | |
| 299 _printColumn(sb, '99th', 15); | |
| 300 _printColumn(sb, 'std-dev', 15); | |
| 301 _printColumn(sb, 'minimum', 15); | |
| 302 _printColumn(sb, 'maximum', 15); | |
| 303 _printColumn(sb, 'total', 15); | |
| 304 print(sb.toString()); | |
| 305 } | |
| 306 | |
| 307 void _printTotals(int keyLen, int totalCount, int totalErrorCount, | |
| 308 int totalUnexpectedResultCount) { | |
| 309 StringBuffer sb = new StringBuffer(); | |
| 310 _printColumn(sb, 'Totals', keyLen); | |
| 311 _printColumn(sb, totalCount.toString(), 6, rightJustified: true); | |
| 312 _printColumn(sb, totalErrorCount.toString(), 6, rightJustified: true); | |
| 313 _printColumn(sb, totalUnexpectedResultCount.toString(), 6, | |
| 314 rightJustified: true); | |
| 315 print(sb.toString()); | |
| 316 } | |
| 317 } | |
| OLD | NEW |