Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: pkg/analysis_server/test/performance/input_converter.dart

Issue 1182933005: analysis server performance measurement - work in progress (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
1 library input.transformer; 5 library input.transformer;
2 6
3 import 'dart:convert'; 7 import 'dart:convert';
8 import 'dart:io';
9
10 import 'package:analysis_server/src/protocol.dart';
11 import 'package:logging/logging.dart';
4 12
5 import 'instrumentation_input_converter.dart'; 13 import 'instrumentation_input_converter.dart';
14 import 'log_file_input_converter.dart';
6 import 'operation.dart'; 15 import 'operation.dart';
7 16
8 final int NINE = '9'.codeUnitAt(0); 17 /**
9 final int ZERO = '0'.codeUnitAt(0); 18 * Common input converter superclass for sharing implementation.
19 */
20 abstract class CommonInputConverter extends Converter<String, Operation> {
21 static final ERROR_PREFIX = 'Server responded with an error: ';
22 final Logger logger = new Logger('InstrumentationInputConverter');
23 final Set<String> eventsSeen = new Set<String>();
24
25 /**
26 * A mapping from request/response id to expected error message.
27 */
28 final Map<String, dynamic> expectedErrors = new Map<String, dynamic>();
29
30 /**
31 * A mapping of source path prefixes
32 * from location where instrumentation or log file was generated
33 * to the target location of the source using during performance measurement.
34 */
35 final Map<String, String> srcPathMap;
36
37 /**
38 * A mapping of current overlay content
39 * parallel to what is in the analysis server
40 * so that we can update the file system.
41 */
42 final Map<String, String> overlays = new Map<String, String>();
43
44 CommonInputConverter(this.srcPathMap);
45
46 /**
47 * Examine recorded responses and record any expected errors.
48 */
49 void recordResponse(Map<String, dynamic> json) {
50 var error = json['error'];
51 if (error != null) {
52 String id = json['id'];
53 print('expected error for $id is $error');
54 }
55 }
56
57 /**
58 * Return an operation for the notification or `null` if none.
59 */
60 Operation convertNotification(Map<String, dynamic> json) {
61 String event = json['event'];
62 if (event == 'server.status') {
63 // {"event":"server.status","params":{"analysis":{"isAnalyzing":false}}}
64 Map<String, dynamic> params = json['params'];
65 if (params != null) {
66 Map<String, dynamic> analysis = params['analysis'];
67 if (analysis != null && analysis['isAnalyzing'] == false) {
68 return new WaitForAnalysisCompleteOperation();
69 }
70 }
71 }
72 if (event == 'server.connected') {
73 // {"event":"server.connected","params":{"version":"1.7.0"}}
74 return new StartServerOperation();
75 }
76 if (eventsSeen.add(event)) {
77 logger.log(Level.INFO, 'Ignored notification: $event\n $json');
78 }
79 return null;
80 }
81
82 /**
83 * Return an operation for the request or `null` if none.
84 */
85 Operation convertRequest(Map<String, dynamic> origJson) {
86 Map<String, dynamic> json = translateSrcPaths(origJson);
87 String method = json['method'];
88 if (method == 'analysis.getHover' ||
Brian Wilkerson 2015/06/15 17:37:47 This is going to get us in trouble. We should mini
danrubel 2015/06/17 15:35:31 Agreed. I implemented it this way for now so that
89 method == 'analysis.setAnalysisRoots' ||
90 method == 'analysis.setPriorityFiles' ||
91 method == 'analysis.setSubscriptions' ||
92 method == 'analysis.updateOptions' ||
93 method == 'completion.getSuggestions' ||
94 method == 'edit.getAssists' ||
95 method == 'edit.getAvailableRefactorings' ||
96 method == 'edit.getFixes' ||
97 method == 'edit.getRefactoring' ||
98 method == 'edit.sortMembers' ||
99 method == 'execution.createContext' ||
100 method == 'execution.deleteContext' ||
101 method == 'execution.mapUri' ||
102 method == 'execution.setSubscriptions' ||
103 method == 'server.getVersion' ||
104 method == 'server.setSubscriptions') {
105 return new RequestOperation(this, json);
106 }
107 // Sanity check operations that modify source
108 // to ensure that the operation is on source in temp space
109 if (method == 'analysis.updateContent') {
110 try {
111 validateSrcPaths(json);
112 } catch (e) {
113 throw '$e\n in $json';
Brian Wilkerson 2015/06/15 17:37:47 Personally, I dislike throwing anything other than
danrubel 2015/06/17 15:35:31 Good suggestion. Done.
114 }
115 // Track overlays in parallel with the analysis server
116 // so that when an overlay is removed, the file can be updated on disk
117 Request request = new Request.fromJson(json);
118 var params = new AnalysisUpdateContentParams.fromRequest(request);
119 params.files.forEach((String path, change) {
120 if (change is AddContentOverlay) {
121 String content = change.content;
122 if (content == null) {
123 throw 'expected new overlay content\n$json';
124 }
125 overlays[path] = content;
126 } else if (change is ChangeContentOverlay) {
127 String content = overlays[path];
128 if (content == null) {
129 throw 'expected cached overlay content\n$json';
130 }
131 overlays[path] = SourceEdit.applySequence(content, change.edits);
132 } else if (change is RemoveContentOverlay) {
133 String content = overlays.remove(path);
134 if (content == null) {
135 throw 'expected cached overlay content\n$json';
136 }
137 validateSrcPath(path);
138 new File(path).writeAsStringSync(content);
139 } else {
140 throw 'unknown overlay change $change\n$json';
141 }
142 });
143 return new RequestOperation(this, json);
144 }
145 throw 'unknown request: $method\n $json';
146 }
147
148 /**
149 * Determine if the given request is expected to fail
150 * and log an exception if not.
151 */
152 void recordErrorResponse(Map<String, dynamic> jsonRequest, exception) {
153 var actualErr;
154 if (exception is UnimplementedError) {
155 if (exception.message.startsWith(ERROR_PREFIX)) {
156 Map<String, dynamic> jsonResponse =
157 JSON.decode(exception.message.substring(ERROR_PREFIX.length));
158 actualErr = jsonResponse['error'];
159 }
160 }
161 String id = jsonRequest['id'];
162 if (id != null && actualErr != null) {
163 var expectedErr = expectedErrors[id];
164 if (expectedErr != null && actualErr == expectedErr) {
165 return;
166 }
167 // if (jsonRequest['method'] == 'edit.sortMembers') {
168 // var params = jsonRequest['params'];
169 // if (params is Map) {
170 // var filePath = params['file'];
171 // if (filePath is String) {
172 // var content = overlays[filePath];
173 // if (content is String) {
174 // logger.log(Level.WARNING, 'sort failed: $filePath\n$content');
175 // }
176 // }
177 // }
178 // }
179 }
180 logger.log(
181 Level.SEVERE, 'Send request failed for $id\n$exception\n$jsonRequest');
182 }
183
184 /**
185 * Return text where any references to
186 * the original source when the instrumentation or log file was generated
187 * are replace with the temporary source used during performance measurement.
188 */
189 String translateSrcPath(String text) {
190 if (text != null) {
191 srcPathMap.forEach((String oldPrefix, String newPrefix) {
192 if (text.startsWith(oldPrefix)) {
193 text = '$newPrefix${text.substring(oldPrefix.length)}';
194 }
195 });
196 }
197 return text;
198 }
199
200 /**
201 * Recursively translate source paths in the specified JSON to reference
202 * the temporary source used during performance measurement rather than
203 * the original source when the instrumentation or log file was generated.
204 */
205 Map<String, dynamic> translateSrcPaths(Map<String, dynamic> origJson) {
Brian Wilkerson 2015/06/15 17:37:47 Consider "translateSrcPaths" --> "translateSrcPath
danrubel 2015/06/17 15:35:31 Done.
206 Map<String, dynamic> result = new Map<String, dynamic>();
207 origJson.forEach((String origKey, value) {
208 String newKey = translateSrcPath(origKey);
209 if (value is String) {
210 value = translateSrcPath(value);
211 } else if (value is List) {
212 value = translateSrcPathsInList(value);
213 } else if (value is Map) {
214 value = translateSrcPaths(value);
215 }
216 result[newKey] = value;
217 });
218 return result;
219 }
220
221 /**
222 * Recursively translate source paths in the specified list to reference
223 * the temporary source used during performance measurement rather than
224 * the original source when the instrumentation or log file was generated.
225 */
226 List translateSrcPathsInList(List list) {
227 List result = [];
228 for (int i = 0; i < list.length; ++i) {
229 var value = list[i];
230 if (value is String) {
Brian Wilkerson 2015/06/15 17:37:47 This logic is repeated at least 4 times. It should
danrubel 2015/06/17 15:35:31 Good point. Reworked this group of methods to remo
231 value = translateSrcPath(value);
232 } else if (value is List) {
233 value = translateSrcPathsInList(value);
234 } else if (value is Map) {
235 value = translateSrcPaths(value);
236 }
237 result.add(value);
238 }
239 return result;
240 }
241
242 /**
243 * Verify that the source path
244 * only reference the temporary source used during performance measurement.
245 */
246 void validateSrcPath(String value) {
247 if (value != null &&
248 value.startsWith('/Users/') &&
249 !srcPathMap.values.any((String prefix) => value.startsWith(prefix))) {
250 throw 'found path referencing source outside temp space\n $value';
251 }
252 }
253
254 /**
255 * Recursively verify that the source paths in the specified [json]
256 * only reference the temporary source used during performance measurement.
257 */
258 void validateSrcPaths(Map<String, dynamic> json) {
259 json.forEach((String key, value) {
260 validateSrcPath(key);
261 if (value is String) {
262 validateSrcPath(value);
263 } else if (value is List) {
264 validateSrcPathsInList(value);
265 } else if (value is Map) {
266 validateSrcPaths(value);
267 }
268 });
269 }
270
271 /**
272 * Recursively verify that the source paths in the specified [list]
273 * only reference the temporary source used during performance measurement.
274 */
275 void validateSrcPathsInList(List list) {
276 for (int i = list.length - 1; i >= 0; --i) {
277 var value = list[i];
278 if (value is String) {
279 validateSrcPath(value);
280 } else if (value is List) {
281 validateSrcPathsInList(value);
282 } else if (value is Map) {
283 validateSrcPaths(value);
284 }
285 }
286 }
287 }
10 288
11 /** 289 /**
12 * [InputConverter] converts an input stream 290 * [InputConverter] converts an input stream
13 * into a series of operations to be sent to the analysis server. 291 * into a series of operations to be sent to the analysis server.
14 * The input stream can be either an instrumenation or log file. 292 * The input stream can be either an instrumenation or log file.
15 */ 293 */
16 class InputConverter extends Converter<String, Operation> { 294 class InputConverter extends Converter<String, Operation> {
295 final Logger logger = new Logger('InputConverter');
296
297 /**
298 * A mapping of source path prefixes
299 * from location where instrumentation or log file was generated
300 * to the target location of the source using during performance measurement.
301 */
302 final Map<String, String> srcPathMap;
17 303
18 /** 304 /**
19 * The number of lines read before the underlying converter was determined 305 * The number of lines read before the underlying converter was determined
20 * or the end of file was reached. 306 * or the end of file was reached.
21 */ 307 */
22 int headerLineCount = 0; 308 int headerLineCount = 0;
23 309
24 /** 310 /**
25 * The underlying converter used to translate lines into operations 311 * The underlying converter used to translate lines into operations
26 * or `null` if it has not yet been determined. 312 * or `null` if it has not yet been determined.
27 */ 313 */
28 Converter<String, Operation> converter; 314 Converter<String, Operation> converter;
29 315
316 /**
317 * [active] is `true` if converting lines to operations
318 * or `false` if an exception has occurred.
319 */
320 bool active = true;
321
322 InputConverter(this.srcPathMap);
323
30 @override 324 @override
31 Operation convert(String line) { 325 Operation convert(String line) {
326 if (!active) {
327 return null;
328 }
32 if (converter != null) { 329 if (converter != null) {
33 return converter.convert(line); 330 try {
331 return converter.convert(line);
332 } catch (e) {
333 active = false;
334 rethrow;
335 }
34 } 336 }
35 if (headerLineCount == 20) { 337 if (headerLineCount == 20) {
36 throw 'Failed to determine input file format'; 338 throw 'Failed to determine input file format';
37 } 339 }
38 if (InstrumentationInputConverter.isFormat(line)) { 340 if (InstrumentationInputConverter.isFormat(line)) {
39 converter = new InstrumentationInputConverter(); 341 converter = new InstrumentationInputConverter(srcPathMap);
40 } else if (LogFileInputConverter.isFormat(line)) { 342 } else if (LogFileInputConverter.isFormat(line)) {
41 converter = new LogFileInputConverter(); 343 converter = new LogFileInputConverter(srcPathMap);
42 } 344 }
43 if (converter != null) { 345 if (converter != null) {
44 return converter.convert(line); 346 return converter.convert(line);
45 } 347 }
46 print(line); 348 logger.log(Level.INFO, 'skipped input line: $line');
47 return null; 349 return null;
48 } 350 }
49 351
50 @override 352 @override
51 _InputSink startChunkedConversion(outSink) { 353 _InputSink startChunkedConversion(outSink) {
52 return new _InputSink(this, outSink); 354 return new _InputSink(this, outSink);
53 } 355 }
54 } 356 }
55 357
56 /**
57 * [LogFileInputConverter] converts a log file stream
58 * into a series of operations to be sent to the analysis server.
59 */
60 class LogFileInputConverter extends Converter<String, Operation> {
61 @override
62 Operation convert(String line) {
63 throw 'not implemented yet';
64 }
65
66 /**
67 * Determine if the given line is from an instrumentation file.
68 * For example:
69 * `1428347977499 <= {"event":"server.connected","params":{"version":"1.6.0"}} `
70 */
71 static bool isFormat(String line) {
72 String timeStampString = _parseTimeStamp(line);
73 int start = timeStampString.length;
74 int end = start + 5;
75 return start > 10 &&
76 line.length > end &&
77 line.substring(start, end) == ' <= {"event":"server.connected"';
78 }
79
80 /**
81 * Parse the given line and return the millisecond timestamp or `null`
82 * if it cannot be determined.
83 */
84 static String _parseTimeStamp(String line) {
85 int index = 0;
86 while (index < line.length) {
87 int code = line.codeUnitAt(index);
88 if (code < ZERO || NINE < code) {
89 return line.substring(0, index);
90 }
91 ++index;
92 }
93 return line;
94 }
95 }
96
97 class _InputSink extends ChunkedConversionSink<String> { 358 class _InputSink extends ChunkedConversionSink<String> {
98 final Converter<String, Operation> converter; 359 final Converter<String, Operation> converter;
99 final outSink; 360 final outSink;
100 361
101 _InputSink(this.converter, this.outSink); 362 _InputSink(this.converter, this.outSink);
102 363
103 @override 364 @override
104 void add(String line) { 365 void add(String line) {
105 Operation op = converter.convert(line); 366 Operation op = converter.convert(line);
106 if (op != null) { 367 if (op != null) {
107 outSink.add(op); 368 outSink.add(op);
108 } 369 }
109 } 370 }
110 371
111 @override 372 @override
112 void close() { 373 void close() {
113 outSink.close(); 374 outSink.close();
114 } 375 }
115 } 376 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698