OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, 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 instrumentation; | |
6 | |
7 import 'dart:convert'; | |
8 | |
9 import 'package:analyzer/task/model.dart'; | |
10 | |
11 /** | |
12 * A container with analysis performance constants. | |
13 */ | |
14 class AnalysisPerformanceKind { | |
15 static const String FULL = 'analysis_full'; | |
16 static const String INCREMENTAL = 'analysis_incremental'; | |
17 } | |
18 | |
19 /** | |
20 * The interface used by client code to communicate with an instrumentation | |
21 * server. | |
22 */ | |
23 abstract class InstrumentationServer { | |
24 /** | |
25 * Pass the given [message] to the instrumentation server so that it will be | |
26 * logged with other messages. | |
27 * | |
28 * This method should be used for most logging. | |
29 */ | |
30 void log(String message); | |
31 | |
32 /** | |
33 * Pass the given [message] to the instrumentation server so that it will be | |
34 * logged with other messages. | |
35 * | |
36 * This method should only be used for logging high priority messages, such as | |
37 * exceptions that cause the server to shutdown. | |
38 */ | |
39 void logWithPriority(String message); | |
40 | |
41 /** | |
42 * Signal that the client is done communicating with the instrumentation | |
43 * server. This method should be invoked exactly one time and no other methods | |
44 * should be invoked on this instance after this method has been invoked. | |
45 */ | |
46 void shutdown(); | |
47 } | |
48 | |
49 /** | |
50 * The interface used by client code to communicate with an instrumentation | |
51 * server by wrapping an [InstrumentationServer]. | |
52 */ | |
53 class InstrumentationService { | |
54 /** | |
55 * An instrumentation service that will not log any instrumentation data. | |
56 */ | |
57 static final InstrumentationService NULL_SERVICE = | |
58 new InstrumentationService(null); | |
59 | |
60 static const String TAG_ANALYSIS_TASK = 'Task'; | |
61 static const String TAG_ERROR = 'Err'; | |
62 static const String TAG_EXCEPTION = 'Ex'; | |
63 static const String TAG_FILE_READ = 'Read'; | |
64 static const String TAG_LOG_ENTRY = 'Log'; | |
65 static const String TAG_NOTIFICATION = 'Noti'; | |
66 static const String TAG_PERFORMANCE = 'Perf'; | |
67 static const String TAG_REQUEST = 'Req'; | |
68 static const String TAG_RESPONSE = 'Res'; | |
69 static const String TAG_SUBPROCESS_START = 'SPStart'; | |
70 static const String TAG_SUBPROCESS_RESULT = 'SPResult'; | |
71 static const String TAG_VERSION = 'Ver'; | |
72 static const String TAG_WATCH_EVENT = 'Watch'; | |
73 | |
74 /** | |
75 * The instrumentation server used to communicate with the server, or `null` | |
76 * if instrumentation data should not be logged. | |
77 */ | |
78 InstrumentationServer _instrumentationServer; | |
79 | |
80 /** | |
81 * Counter used to generate unique ID's for [logSubprocessStart]. | |
82 */ | |
83 int _subprocessCounter = 0; | |
84 | |
85 /** | |
86 * Initialize a newly created instrumentation service to comunicate with the | |
87 * given [instrumentationServer]. | |
88 */ | |
89 InstrumentationService(this._instrumentationServer); | |
90 | |
91 /** | |
92 * Return `true` if this [InstrumentationService] was initialized with a | |
93 * non-`null` server (and hence instrumentation is active). | |
94 */ | |
95 bool get isActive => _instrumentationServer != null; | |
96 | |
97 /** | |
98 * The current time, expressed as a decimal encoded number of milliseconds. | |
99 */ | |
100 String get _timestamp => new DateTime.now().millisecondsSinceEpoch.toString(); | |
101 | |
102 /** | |
103 * Log that the given analysis [task] is being performed in the given | |
104 * [context]. | |
105 */ | |
106 void logAnalysisTask(String context, dynamic task) { | |
107 // TODO(brianwilkerson) When the old task model is removed, change the | |
108 // parameter type to AnalysisTask. | |
109 if (_instrumentationServer != null) { | |
110 String description = | |
111 (task is AnalysisTask) ? task.description : task.toString(); | |
112 _instrumentationServer | |
113 .log(_join([TAG_ANALYSIS_TASK, context, description])); | |
114 } | |
115 } | |
116 | |
117 /** | |
118 * Log the fact that an error, described by the given [message], has occurred. | |
119 */ | |
120 void logError(String message) { | |
121 _log(TAG_ERROR, message); | |
122 } | |
123 | |
124 /** | |
125 * Log that the given non-priority [exception] was thrown, with the given | |
126 * [stackTrace]. | |
127 */ | |
128 void logException(dynamic exception, StackTrace stackTrace) { | |
129 if (_instrumentationServer != null) { | |
130 String message = _toString(exception); | |
131 String trace = _toString(stackTrace); | |
132 _instrumentationServer.log(_join([TAG_EXCEPTION, message, trace])); | |
133 } | |
134 } | |
135 | |
136 /** | |
137 * Log that the contents of the file with the given [path] were read. The file | |
138 * had the given [content] and [modificationTime]. | |
139 */ | |
140 void logFileRead(String path, int modificationTime, String content) { | |
141 if (_instrumentationServer != null) { | |
142 String timeStamp = _toString(modificationTime); | |
143 _instrumentationServer | |
144 .log(_join([TAG_FILE_READ, path, timeStamp, content])); | |
145 } | |
146 } | |
147 | |
148 /** | |
149 * Log that a log entry that was written to the analysis engine's log. The log | |
150 * entry has the given [level] and [message], and was created at the given | |
151 * [time]. | |
152 */ | |
153 void logLogEntry(String level, DateTime time, String message) { | |
154 if (_instrumentationServer != null) { | |
155 String timeStamp = | |
156 time == null ? 'null' : time.millisecondsSinceEpoch.toString(); | |
157 _instrumentationServer | |
158 .log(_join([TAG_LOG_ENTRY, level, timeStamp, message])); | |
159 } | |
160 } | |
161 | |
162 /** | |
163 * Log that a notification has been sent to the client. | |
164 */ | |
165 void logNotification(String notification) { | |
166 _log(TAG_NOTIFICATION, notification); | |
167 } | |
168 | |
169 /** | |
170 * Log the given performance fact. | |
171 */ | |
172 void logPerformance(String kind, Stopwatch sw, String message) { | |
173 sw.stop(); | |
174 String elapsed = sw.elapsedMilliseconds.toString(); | |
175 if (_instrumentationServer != null) { | |
176 _instrumentationServer | |
177 .log(_join([TAG_PERFORMANCE, kind, elapsed, message])); | |
178 } | |
179 } | |
180 | |
181 /** | |
182 * Log that the given priority [exception] was thrown, with the given | |
183 * [stackTrace]. | |
184 */ | |
185 void logPriorityException(dynamic exception, StackTrace stackTrace) { | |
186 if (_instrumentationServer != null) { | |
187 String message = _toString(exception); | |
188 String trace = _toString(stackTrace); | |
189 _instrumentationServer | |
190 .logWithPriority(_join([TAG_EXCEPTION, message, trace])); | |
191 } | |
192 } | |
193 | |
194 /** | |
195 * Log that a request has been sent to the client. | |
196 */ | |
197 void logRequest(String request) { | |
198 _log(TAG_REQUEST, request); | |
199 } | |
200 | |
201 /** | |
202 * Log that a response has been sent to the client. | |
203 */ | |
204 void logResponse(String response) { | |
205 _log(TAG_RESPONSE, response); | |
206 } | |
207 | |
208 /** | |
209 * Log the result of executing a subprocess. [subprocessId] should be the | |
210 * unique IDreturned by [logSubprocessStart]. | |
211 */ | |
212 void logSubprocessResult( | |
213 int subprocessId, int exitCode, String stdout, String stderr) { | |
214 if (_instrumentationServer != null) { | |
215 _instrumentationServer.log(_join([ | |
216 TAG_SUBPROCESS_RESULT, | |
217 subprocessId.toString(), | |
218 exitCode.toString(), | |
219 JSON.encode(stdout), | |
220 JSON.encode(stderr) | |
221 ])); | |
222 } | |
223 } | |
224 | |
225 /** | |
226 * Log that the given subprocess is about to be executed. Returns a unique | |
227 * identifier that can be used to identify the subprocess for later log | |
228 * entries. | |
229 */ | |
230 int logSubprocessStart( | |
231 String executablePath, List<String> arguments, String workingDirectory) { | |
232 int subprocessId = _subprocessCounter++; | |
233 if (_instrumentationServer != null) { | |
234 _instrumentationServer.log(_join([ | |
235 TAG_SUBPROCESS_START, | |
236 subprocessId.toString(), | |
237 executablePath, | |
238 workingDirectory, | |
239 JSON.encode(arguments) | |
240 ])); | |
241 } | |
242 return subprocessId; | |
243 } | |
244 | |
245 /** | |
246 * Signal that the client has started analysis server. | |
247 * This method should be invoked exactly one time. | |
248 */ | |
249 void logVersion(String uuid, String clientId, String clientVersion, | |
250 String serverVersion, String sdkVersion) { | |
251 String normalize(String value) => | |
252 value != null && value.length > 0 ? value : 'unknown'; | |
253 | |
254 if (_instrumentationServer != null) { | |
255 _instrumentationServer.logWithPriority(_join([ | |
256 TAG_VERSION, | |
257 uuid, | |
258 normalize(clientId), | |
259 normalize(clientVersion), | |
260 serverVersion, | |
261 sdkVersion | |
262 ])); | |
263 } | |
264 } | |
265 | |
266 /** | |
267 * Log that the file system watcher sent an event. The [folderPath] is the | |
268 * path to the folder containing the changed file, the [filePath] is the path | |
269 * of the file that changed, and the [changeType] indicates what kind of | |
270 * change occurred. | |
271 */ | |
272 void logWatchEvent(String folderPath, String filePath, String changeType) { | |
273 if (_instrumentationServer != null) { | |
274 _instrumentationServer | |
275 .log(_join([TAG_WATCH_EVENT, folderPath, filePath, changeType])); | |
276 } | |
277 } | |
278 | |
279 /** | |
280 * Signal that the client is done communicating with the instrumentation | |
281 * server. This method should be invoked exactly one time and no other methods | |
282 * should be invoked on this instance after this method has been invoked. | |
283 */ | |
284 void shutdown() { | |
285 if (_instrumentationServer != null) { | |
286 _instrumentationServer.shutdown(); | |
287 _instrumentationServer = null; | |
288 } | |
289 } | |
290 | |
291 /** | |
292 * Write an escaped version of the given [field] to the given [buffer]. | |
293 */ | |
294 void _escape(StringBuffer buffer, String field) { | |
295 int index = field.indexOf(':'); | |
296 if (index < 0) { | |
297 buffer.write(field); | |
298 return; | |
299 } | |
300 int start = 0; | |
301 while (index >= 0) { | |
302 buffer.write(field.substring(start, index)); | |
303 buffer.write('::'); | |
304 start = index + 1; | |
305 index = field.indexOf(':', start); | |
306 } | |
307 buffer.write(field.substring(start)); | |
308 } | |
309 | |
310 /** | |
311 * Return the result of joining the values of the given fields, escaping the | |
312 * separator character by doubling it. | |
313 */ | |
314 String _join(List<String> fields) { | |
315 StringBuffer buffer = new StringBuffer(); | |
316 buffer.write(_timestamp); | |
317 for (String field in fields) { | |
318 buffer.write(':'); | |
319 _escape(buffer, field); | |
320 } | |
321 return buffer.toString(); | |
322 } | |
323 | |
324 /** | |
325 * Log the given message with the given tag. | |
326 */ | |
327 void _log(String tag, String message) { | |
328 if (_instrumentationServer != null) { | |
329 _instrumentationServer.log(_join([tag, message])); | |
330 } | |
331 } | |
332 | |
333 /** | |
334 * Convert the given [object] to a string. | |
335 */ | |
336 String _toString(Object object) { | |
337 if (object == null) { | |
338 return 'null'; | |
339 } | |
340 return object.toString(); | |
341 } | |
342 } | |
343 | |
344 /** | |
345 * An [InstrumentationServer] that sends messages to multiple instances. | |
346 */ | |
347 class MulticastInstrumentationServer implements InstrumentationServer { | |
348 final List<InstrumentationServer> _servers; | |
349 | |
350 MulticastInstrumentationServer(this._servers); | |
351 | |
352 @override | |
353 void log(String message) { | |
354 for (InstrumentationServer server in _servers) { | |
355 server.log(message); | |
356 } | |
357 } | |
358 | |
359 @override | |
360 void logWithPriority(String message) { | |
361 for (InstrumentationServer server in _servers) { | |
362 server.logWithPriority(message); | |
363 } | |
364 } | |
365 | |
366 @override | |
367 void shutdown() { | |
368 for (InstrumentationServer server in _servers) { | |
369 server.shutdown(); | |
370 } | |
371 } | |
372 } | |
OLD | NEW |