| 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 |