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

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: address comments 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/constants.dart';
11 import 'package:analysis_server/src/protocol.dart';
12 import 'package:analyzer/src/generated/java_engine.dart';
13 import 'package:logging/logging.dart';
4 14
5 import 'instrumentation_input_converter.dart'; 15 import 'instrumentation_input_converter.dart';
16 import 'log_file_input_converter.dart';
6 import 'operation.dart'; 17 import 'operation.dart';
7 18
8 final int NINE = '9'.codeUnitAt(0); 19 /**
9 final int ZERO = '0'.codeUnitAt(0); 20 * Common input converter superclass for sharing implementation.
21 */
22 abstract class CommonInputConverter extends Converter<String, Operation> {
23 static final ERROR_PREFIX = 'Server responded with an error: ';
24 final Logger logger = new Logger('InstrumentationInputConverter');
25 final Set<String> eventsSeen = new Set<String>();
26
27 /**
28 * A mapping from request/response id to expected error message.
29 */
30 final Map<String, dynamic> expectedErrors = new Map<String, dynamic>();
31
32 /**
33 * A mapping of source path prefixes
34 * from location where instrumentation or log file was generated
35 * to the target location of the source using during performance measurement.
36 */
37 final Map<String, String> srcPathMap;
38
39 /**
40 * A mapping of current overlay content
41 * parallel to what is in the analysis server
42 * so that we can update the file system.
43 */
44 final Map<String, String> overlays = new Map<String, String>();
45
46 CommonInputConverter(this.srcPathMap);
47
48 /**
49 * Return an operation for the notification or `null` if none.
50 */
51 Operation convertNotification(Map<String, dynamic> json) {
52 String event = json['event'];
53 if (event == SERVER_STATUS) {
54 // {"event":"server.status","params":{"analysis":{"isAnalyzing":false}}}
55 Map<String, dynamic> params = json['params'];
56 if (params != null) {
57 Map<String, dynamic> analysis = params['analysis'];
58 if (analysis != null && analysis['isAnalyzing'] == false) {
59 return new WaitForAnalysisCompleteOperation();
60 }
61 }
62 }
63 if (event == SERVER_CONNECTED) {
64 // {"event":"server.connected","params":{"version":"1.7.0"}}
65 return new StartServerOperation();
66 }
67 if (eventsSeen.add(event)) {
68 logger.log(Level.INFO, 'Ignored notification: $event\n $json');
69 }
70 return null;
71 }
72
73 /**
74 * Return an operation for the request or `null` if none.
75 */
76 Operation convertRequest(Map<String, dynamic> origJson) {
77 Map<String, dynamic> json = translateSrcPaths(origJson);
78 String method = json['method'];
79 if (method == ANALYSIS_GET_HOVER ||
80 method == ANALYSIS_SET_ANALYSIS_ROOTS ||
81 method == ANALYSIS_SET_PRIORITY_FILES ||
82 method == ANALYSIS_SET_SUBSCRIPTIONS ||
83 method == ANALYSIS_UPDATE_OPTIONS ||
84 method == COMPLETION_GET_SUGGESTIONS ||
85 method == EDIT_GET_ASSISTS ||
86 method == EDIT_GET_AVAILABLE_REFACTORINGS ||
87 method == EDIT_GET_FIXES ||
88 method == EDIT_GET_REFACTORING ||
89 method == EDIT_SORT_MEMBERS ||
90 method == EXECUTION_CREATE_CONTEXT ||
91 method == EXECUTION_DELETE_CONTEXT ||
92 method == EXECUTION_MAP_URI ||
93 method == EXECUTION_SET_SUBSCRIPTIONS ||
94 method == SERVER_GET_VERSION ||
95 method == SERVER_SET_SUBSCRIPTIONS) {
96 return new RequestOperation(this, json);
97 }
98 // Sanity check operations that modify source
99 // to ensure that the operation is on source in temp space
100 if (method == ANALYSIS_UPDATE_CONTENT) {
101 try {
102 validateSrcPaths(json);
103 } catch (e, s) {
104 throw new AnalysisException('invalid src path in update request\n$json',
105 new CaughtException(e, s));
106 }
107 // Track overlays in parallel with the analysis server
108 // so that when an overlay is removed, the file can be updated on disk
109 Request request = new Request.fromJson(json);
110 var params = new AnalysisUpdateContentParams.fromRequest(request);
111 params.files.forEach((String path, change) {
112 if (change is AddContentOverlay) {
113 String content = change.content;
114 if (content == null) {
115 throw 'expected new overlay content\n$json';
116 }
117 overlays[path] = content;
118 } else if (change is ChangeContentOverlay) {
119 String content = overlays[path];
120 if (content == null) {
121 throw 'expected cached overlay content\n$json';
122 }
123 overlays[path] = SourceEdit.applySequence(content, change.edits);
124 } else if (change is RemoveContentOverlay) {
125 String content = overlays.remove(path);
126 if (content == null) {
127 throw 'expected cached overlay content\n$json';
128 }
129 validateSrcPaths(path);
130 new File(path).writeAsStringSync(content);
131 } else {
132 throw 'unknown overlay change $change\n$json';
133 }
134 });
135 return new RequestOperation(this, json);
136 }
137 throw 'unknown request: $method\n $json';
138 }
139
140 /**
141 * Determine if the given request is expected to fail
142 * and log an exception if not.
143 */
144 void recordErrorResponse(Map<String, dynamic> jsonRequest, exception) {
145 var actualErr;
146 if (exception is UnimplementedError) {
147 if (exception.message.startsWith(ERROR_PREFIX)) {
148 Map<String, dynamic> jsonResponse =
149 JSON.decode(exception.message.substring(ERROR_PREFIX.length));
150 actualErr = jsonResponse['error'];
151 }
152 }
153 String id = jsonRequest['id'];
154 if (id != null && actualErr != null) {
155 var expectedErr = expectedErrors[id];
156 if (expectedErr != null && actualErr == expectedErr) {
157 return;
158 }
159 // if (jsonRequest['method'] == EDIT_SORT_MEMBERS) {
160 // var params = jsonRequest['params'];
161 // if (params is Map) {
162 // var filePath = params['file'];
163 // if (filePath is String) {
164 // var content = overlays[filePath];
165 // if (content is String) {
166 // logger.log(Level.WARNING, 'sort failed: $filePath\n$content');
167 // }
168 // }
169 // }
170 // }
171 }
172 logger.log(
173 Level.SEVERE, 'Send request failed for $id\n$exception\n$jsonRequest');
174 }
175
176 /**
177 * Examine recorded responses and record any expected errors.
178 */
179 void recordResponse(Map<String, dynamic> json) {
180 var error = json['error'];
181 if (error != null) {
182 String id = json['id'];
183 print('expected error for $id is $error');
184 }
185 }
186
187 /**
188 * Recursively translate source paths in the specified JSON to reference
189 * the temporary source used during performance measurement rather than
190 * the original source when the instrumentation or log file was generated.
191 */
192 translateSrcPaths(json) {
193 if (json is String) {
194 String result = json;
195 srcPathMap.forEach((String oldPrefix, String newPrefix) {
196 if (json.startsWith(oldPrefix)) {
197 result = '$newPrefix${json.substring(oldPrefix.length)}';
198 }
199 });
200 return result;
201 }
202 if (json is List) {
203 List result = [];
204 for (int i = 0; i < json.length; ++i) {
205 result.add(translateSrcPaths(json[i]));
206 }
207 return result;
208 }
209 if (json is Map) {
210 Map<String, dynamic> result = new Map<String, dynamic>();
211 json.forEach((String origKey, value) {
212 result[translateSrcPaths(origKey)] = translateSrcPaths(value);
213 });
214 return result;
215 }
216 return json;
217 }
218
219 /**
220 * Recursively verify that the source paths in the specified JSON
221 * only reference the temporary source used during performance measurement.
222 */
223 void validateSrcPaths(json) {
224 if (json is String) {
225 if (json != null &&
226 json.startsWith('/Users/') &&
227 !srcPathMap.values.any((String prefix) => json.startsWith(prefix))) {
228 throw 'found path referencing source outside temp space\n$json';
229 }
230 } else if (json is List) {
231 for (int i = json.length - 1; i >= 0; --i) {
232 validateSrcPaths(json[i]);
233 }
234 } else if (json is Map) {
235 json.forEach((String key, value) {
236 validateSrcPaths(key);
237 validateSrcPaths(value);
238 });
239 }
240 }
241 }
10 242
11 /** 243 /**
12 * [InputConverter] converts an input stream 244 * [InputConverter] converts an input stream
13 * into a series of operations to be sent to the analysis server. 245 * into a series of operations to be sent to the analysis server.
14 * The input stream can be either an instrumenation or log file. 246 * The input stream can be either an instrumenation or log file.
15 */ 247 */
16 class InputConverter extends Converter<String, Operation> { 248 class InputConverter extends Converter<String, Operation> {
249 final Logger logger = new Logger('InputConverter');
250
251 /**
252 * A mapping of source path prefixes
253 * from location where instrumentation or log file was generated
254 * to the target location of the source using during performance measurement.
255 */
256 final Map<String, String> srcPathMap;
17 257
18 /** 258 /**
19 * The number of lines read before the underlying converter was determined 259 * The number of lines read before the underlying converter was determined
20 * or the end of file was reached. 260 * or the end of file was reached.
21 */ 261 */
22 int headerLineCount = 0; 262 int headerLineCount = 0;
23 263
24 /** 264 /**
25 * The underlying converter used to translate lines into operations 265 * The underlying converter used to translate lines into operations
26 * or `null` if it has not yet been determined. 266 * or `null` if it has not yet been determined.
27 */ 267 */
28 Converter<String, Operation> converter; 268 Converter<String, Operation> converter;
29 269
270 /**
271 * [active] is `true` if converting lines to operations
272 * or `false` if an exception has occurred.
273 */
274 bool active = true;
275
276 InputConverter(this.srcPathMap);
277
30 @override 278 @override
31 Operation convert(String line) { 279 Operation convert(String line) {
280 if (!active) {
281 return null;
282 }
32 if (converter != null) { 283 if (converter != null) {
33 return converter.convert(line); 284 try {
285 return converter.convert(line);
286 } catch (e) {
287 active = false;
288 rethrow;
289 }
34 } 290 }
35 if (headerLineCount == 20) { 291 if (headerLineCount == 20) {
36 throw 'Failed to determine input file format'; 292 throw 'Failed to determine input file format';
37 } 293 }
38 if (InstrumentationInputConverter.isFormat(line)) { 294 if (InstrumentationInputConverter.isFormat(line)) {
39 converter = new InstrumentationInputConverter(); 295 converter = new InstrumentationInputConverter(srcPathMap);
40 } else if (LogFileInputConverter.isFormat(line)) { 296 } else if (LogFileInputConverter.isFormat(line)) {
41 converter = new LogFileInputConverter(); 297 converter = new LogFileInputConverter(srcPathMap);
42 } 298 }
43 if (converter != null) { 299 if (converter != null) {
44 return converter.convert(line); 300 return converter.convert(line);
45 } 301 }
46 print(line); 302 logger.log(Level.INFO, 'skipped input line: $line');
47 return null; 303 return null;
48 } 304 }
49 305
50 @override 306 @override
51 _InputSink startChunkedConversion(outSink) { 307 _InputSink startChunkedConversion(outSink) {
52 return new _InputSink(this, outSink); 308 return new _InputSink(this, outSink);
53 } 309 }
54 } 310 }
55 311
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> { 312 class _InputSink extends ChunkedConversionSink<String> {
98 final Converter<String, Operation> converter; 313 final Converter<String, Operation> converter;
99 final outSink; 314 final outSink;
100 315
101 _InputSink(this.converter, this.outSink); 316 _InputSink(this.converter, this.outSink);
102 317
103 @override 318 @override
104 void add(String line) { 319 void add(String line) {
105 Operation op = converter.convert(line); 320 Operation op = converter.convert(line);
106 if (op != null) { 321 if (op != null) {
107 outSink.add(op); 322 outSink.add(op);
108 } 323 }
109 } 324 }
110 325
111 @override 326 @override
112 void close() { 327 void close() {
113 outSink.close(); 328 outSink.close();
114 } 329 }
115 } 330 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698