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 |