| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2016, 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 import 'dart:convert'; | |
| 6 import 'dart:io'; | |
| 7 import 'dart:math'; | |
| 8 | |
| 9 import 'package:analysis_server/protocol/protocol.dart'; | |
| 10 import 'package:analysis_server/protocol/protocol_generated.dart' | |
| 11 hide AnalysisOptions; | |
| 12 import 'package:analysis_server/src/analysis_server.dart'; | |
| 13 import 'package:analysis_server/src/domain_completion.dart'; | |
| 14 import 'package:analysis_server/src/domain_diagnostic.dart'; | |
| 15 import 'package:analysis_server/src/domain_execution.dart'; | |
| 16 import 'package:analysis_server/src/plugin/plugin_manager.dart'; | |
| 17 import 'package:analysis_server/src/server/http_server.dart'; | |
| 18 import 'package:analysis_server/src/services/completion/completion_performance.d
art'; | |
| 19 import 'package:analysis_server/src/socket_server.dart'; | |
| 20 import 'package:analyzer/exception/exception.dart'; | |
| 21 import 'package:analyzer/file_system/file_system.dart'; | |
| 22 import 'package:analyzer/instrumentation/instrumentation.dart'; | |
| 23 import 'package:analyzer/source/error_processor.dart'; | |
| 24 import 'package:analyzer/source/package_map_resolver.dart'; | |
| 25 import 'package:analyzer/source/sdk_ext.dart'; | |
| 26 import 'package:analyzer/src/context/source.dart'; | |
| 27 import 'package:analyzer/src/dart/analysis/driver.dart'; | |
| 28 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
| 29 import 'package:analyzer/src/generated/engine.dart'; | |
| 30 import 'package:analyzer/src/generated/sdk.dart'; | |
| 31 import 'package:analyzer/src/generated/source.dart'; | |
| 32 import 'package:analyzer/src/generated/utilities_general.dart'; | |
| 33 import 'package:analyzer/src/services/lint.dart'; | |
| 34 import 'package:analyzer/task/model.dart'; | |
| 35 import 'package:path/path.dart' as path; | |
| 36 import 'package:plugin/plugin.dart'; | |
| 37 | |
| 38 String _writeWithSeparators(int value) { | |
| 39 // TODO(devoncarew): Replace with the implementation from package:intl. | |
| 40 String str = value.toString(); | |
| 41 int pos = 3; | |
| 42 while (str.length > pos) { | |
| 43 int len = str.length; | |
| 44 str = '${str.substring(0, len - pos)},${str.substring(len - pos)}'; | |
| 45 pos += 4; | |
| 46 } | |
| 47 return str; | |
| 48 } | |
| 49 | |
| 50 /** | |
| 51 * A function that can be used to generate HTML output into the given [buffer]. | |
| 52 * The HTML that is generated must be valid (special characters must already be | |
| 53 * encoded). | |
| 54 */ | |
| 55 typedef void HtmlGenerator(StringBuffer buffer); | |
| 56 | |
| 57 /** | |
| 58 * Instances of the class [GetHandler2] handle GET requests. | |
| 59 */ | |
| 60 class GetHandler2 implements AbstractGetHandler { | |
| 61 /** | |
| 62 * The path used to request overall performance information. | |
| 63 */ | |
| 64 static const String ANALYSIS_PERFORMANCE_PATH = '/perf/analysis'; | |
| 65 | |
| 66 /** | |
| 67 * The path used to request code completion information. | |
| 68 */ | |
| 69 static const String COMPLETION_PATH = '/completion'; | |
| 70 | |
| 71 /** | |
| 72 * The path used to request communication performance information. | |
| 73 */ | |
| 74 static const String COMMUNICATION_PERFORMANCE_PATH = '/perf/communication'; | |
| 75 | |
| 76 /** | |
| 77 * The path used to request information about a specific context. | |
| 78 */ | |
| 79 static const String CONTEXT_PATH = '/context'; | |
| 80 | |
| 81 /** | |
| 82 * The path used to request an overlay contents. | |
| 83 */ | |
| 84 static const String OVERLAY_PATH = '/overlay'; | |
| 85 | |
| 86 /** | |
| 87 * The path used to request overlays information. | |
| 88 */ | |
| 89 static const String OVERLAYS_PATH = '/overlays'; | |
| 90 | |
| 91 /** | |
| 92 * The path used to request the status of the analysis server as a whole. | |
| 93 */ | |
| 94 static const String STATUS_PATH = '/status'; | |
| 95 | |
| 96 /** | |
| 97 * Query parameter used to represent the context to search for. | |
| 98 */ | |
| 99 static const String CONTEXT_QUERY_PARAM = 'context'; | |
| 100 | |
| 101 /** | |
| 102 * Query parameter used to represent the path of an overlayed file. | |
| 103 */ | |
| 104 static const String PATH_PARAM = 'path'; | |
| 105 | |
| 106 static final ContentType _htmlContent = | |
| 107 new ContentType("text", "html", charset: "utf-8"); | |
| 108 | |
| 109 /** | |
| 110 * The socket server whose status is to be reported on. | |
| 111 */ | |
| 112 SocketServer _server; | |
| 113 | |
| 114 /** | |
| 115 * Buffer containing strings printed by the analysis server. | |
| 116 */ | |
| 117 List<String> _printBuffer; | |
| 118 | |
| 119 /** | |
| 120 * Contents of overlay files. | |
| 121 */ | |
| 122 final Map<String, String> _overlayContents = <String, String>{}; | |
| 123 | |
| 124 /** | |
| 125 * Handler for diagnostics requests. | |
| 126 */ | |
| 127 DiagnosticDomainHandler _diagnosticHandler; | |
| 128 | |
| 129 /** | |
| 130 * Initialize a newly created handler for GET requests. | |
| 131 */ | |
| 132 GetHandler2(this._server, this._printBuffer); | |
| 133 | |
| 134 DiagnosticDomainHandler get diagnosticHandler { | |
| 135 if (_diagnosticHandler == null) { | |
| 136 _diagnosticHandler = new DiagnosticDomainHandler(_server.analysisServer); | |
| 137 } | |
| 138 return _diagnosticHandler; | |
| 139 } | |
| 140 | |
| 141 /** | |
| 142 * Return the active [CompletionDomainHandler] | |
| 143 * or `null` if either analysis server is not running | |
| 144 * or there is no completion domain handler. | |
| 145 */ | |
| 146 CompletionDomainHandler get _completionDomainHandler { | |
| 147 AnalysisServer analysisServer = _server.analysisServer; | |
| 148 if (analysisServer == null) { | |
| 149 return null; | |
| 150 } | |
| 151 return analysisServer.handlers | |
| 152 .firstWhere((h) => h is CompletionDomainHandler, orElse: () => null); | |
| 153 } | |
| 154 | |
| 155 /** | |
| 156 * Handle a GET request received by the HTTP server. | |
| 157 */ | |
| 158 void handleGetRequest(HttpRequest request) { | |
| 159 String path = request.uri.path; | |
| 160 if (path == '/') { | |
| 161 _returnRedirect(request, STATUS_PATH); | |
| 162 } else if (path == STATUS_PATH) { | |
| 163 _returnServerStatus(request); | |
| 164 } else if (path == ANALYSIS_PERFORMANCE_PATH) { | |
| 165 _returnAnalysisPerformance(request); | |
| 166 } else if (path == COMPLETION_PATH) { | |
| 167 _returnCompletionInfo(request); | |
| 168 } else if (path == COMMUNICATION_PERFORMANCE_PATH) { | |
| 169 _returnCommunicationPerformance(request); | |
| 170 } else if (path == CONTEXT_PATH) { | |
| 171 _returnContextInfo(request); | |
| 172 } else if (path == OVERLAY_PATH) { | |
| 173 _returnOverlayContents(request); | |
| 174 } else if (path == OVERLAYS_PATH) { | |
| 175 _returnOverlaysInfo(request); | |
| 176 } else { | |
| 177 _returnUnknownRequest(request); | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Return the folder being managed by the given [analysisServer] that matches | |
| 183 * the given [contextFilter], or `null` if there is none. | |
| 184 */ | |
| 185 Folder _findFolder(AnalysisServer analysisServer, String contextFilter) { | |
| 186 return analysisServer.driverMap.keys.firstWhere( | |
| 187 (Folder folder) => folder.path == contextFilter, | |
| 188 orElse: () => null); | |
| 189 } | |
| 190 | |
| 191 /** | |
| 192 * Return `true` if the given analysis [driver] has at least one entry with | |
| 193 * an exception. | |
| 194 */ | |
| 195 bool _hasException(AnalysisDriver driver) { | |
| 196 // if (driver == null) { | |
| 197 // return false; | |
| 198 // } | |
| 199 // MapIterator<AnalysisTarget, CacheEntry> iterator = | |
| 200 // context.analysisCache.iterator(); | |
| 201 // while (iterator.moveNext()) { | |
| 202 // CacheEntry entry = iterator.value; | |
| 203 // if (entry == null || entry.exception != null) { | |
| 204 // return true; | |
| 205 // } | |
| 206 // } | |
| 207 // TODO(scheglov) | |
| 208 return false; | |
| 209 } | |
| 210 | |
| 211 /** | |
| 212 * Return a response displaying overall performance information. | |
| 213 */ | |
| 214 void _returnAnalysisPerformance(HttpRequest request) { | |
| 215 AnalysisServer analysisServer = _server.analysisServer; | |
| 216 if (analysisServer == null) { | |
| 217 return _returnFailure(request, 'Analysis server is not running'); | |
| 218 } | |
| 219 _writeResponse(request, (StringBuffer buffer) { | |
| 220 _writePage(buffer, 'Analysis Performance', [], (StringBuffer buffer) { | |
| 221 buffer.write('<h3>Analysis Performance</h3>'); | |
| 222 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 223 // | |
| 224 // Write performance tags. | |
| 225 // | |
| 226 buffer.write('<p><b>Performance tag data</b></p>'); | |
| 227 buffer.write( | |
| 228 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 229 _writeRow(buffer, ['Time (in ms)', 'Percent', 'Tag name'], | |
| 230 header: true); | |
| 231 // prepare sorted tags | |
| 232 List<PerformanceTag> tags = PerformanceTag.all.toList(); | |
| 233 tags.remove(ServerPerformanceStatistics.idle); | |
| 234 tags.sort((a, b) => b.elapsedMs - a.elapsedMs); | |
| 235 // prepare total time | |
| 236 int totalTagTime = 0; | |
| 237 tags.forEach((PerformanceTag tag) { | |
| 238 totalTagTime += tag.elapsedMs; | |
| 239 }); | |
| 240 // write rows | |
| 241 void writeRow(PerformanceTag tag) { | |
| 242 double percent = (tag.elapsedMs * 100) / totalTagTime; | |
| 243 String percentStr = '${percent.toStringAsFixed(2)}%'; | |
| 244 _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label], | |
| 245 classes: ["right", "right", null]); | |
| 246 } | |
| 247 | |
| 248 tags.forEach(writeRow); | |
| 249 buffer.write('</table>'); | |
| 250 }, (StringBuffer buffer) { | |
| 251 // | |
| 252 // Write task model timing information. | |
| 253 // | |
| 254 buffer.write('<p><b>Task performance data</b></p>'); | |
| 255 buffer.write( | |
| 256 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 257 _writeRow( | |
| 258 buffer, | |
| 259 [ | |
| 260 'Task Name', | |
| 261 'Count', | |
| 262 'Total Time (in ms)', | |
| 263 'Average Time (in ms)' | |
| 264 ], | |
| 265 header: true); | |
| 266 | |
| 267 Map<Type, int> countMap = AnalysisTask.countMap; | |
| 268 Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap; | |
| 269 List<Type> taskClasses = stopwatchMap.keys.toList(); | |
| 270 taskClasses.sort((Type first, Type second) => | |
| 271 first.toString().compareTo(second.toString())); | |
| 272 int totalTaskTime = 0; | |
| 273 taskClasses.forEach((Type taskClass) { | |
| 274 int count = countMap[taskClass]; | |
| 275 if (count == null) { | |
| 276 count = 0; | |
| 277 } | |
| 278 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds; | |
| 279 totalTaskTime += taskTime; | |
| 280 _writeRow(buffer, [ | |
| 281 taskClass.toString(), | |
| 282 count, | |
| 283 taskTime, | |
| 284 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3) | |
| 285 ], classes: [ | |
| 286 null, | |
| 287 "right", | |
| 288 "right", | |
| 289 "right" | |
| 290 ]); | |
| 291 }); | |
| 292 _writeRow(buffer, ['Total', '-', totalTaskTime, '-'], | |
| 293 classes: [null, "right", "right", "right"]); | |
| 294 buffer.write('</table>'); | |
| 295 }); | |
| 296 }); | |
| 297 }); | |
| 298 } | |
| 299 | |
| 300 /** | |
| 301 * Return a response displaying overall performance information. | |
| 302 */ | |
| 303 void _returnCommunicationPerformance(HttpRequest request) { | |
| 304 AnalysisServer analysisServer = _server.analysisServer; | |
| 305 if (analysisServer == null) { | |
| 306 return _returnFailure(request, 'Analysis server is not running'); | |
| 307 } | |
| 308 _writeResponse(request, (StringBuffer buffer) { | |
| 309 _writePage(buffer, 'Communication Performance', [], | |
| 310 (StringBuffer buffer) { | |
| 311 buffer.write('<h3>Communication Performance</h3>'); | |
| 312 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 313 ServerPerformance perf = analysisServer.performanceDuringStartup; | |
| 314 int requestCount = perf.requestCount; | |
| 315 num averageLatency = requestCount > 0 | |
| 316 ? (perf.requestLatency / requestCount).round() | |
| 317 : 0; | |
| 318 int maximumLatency = perf.maxLatency; | |
| 319 num slowRequestPercent = requestCount > 0 | |
| 320 ? (perf.slowRequestCount * 100 / requestCount).round() | |
| 321 : 0; | |
| 322 buffer.write('<h4>Startup</h4>'); | |
| 323 buffer.write('<table>'); | |
| 324 _writeRow(buffer, [requestCount, 'requests'], | |
| 325 classes: ["right", null]); | |
| 326 _writeRow(buffer, [averageLatency, 'ms average latency'], | |
| 327 classes: ["right", null]); | |
| 328 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | |
| 329 classes: ["right", null]); | |
| 330 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | |
| 331 classes: ["right", null]); | |
| 332 if (analysisServer.performanceAfterStartup != null) { | |
| 333 int startupTime = analysisServer.performanceAfterStartup.startTime - | |
| 334 perf.startTime; | |
| 335 _writeRow( | |
| 336 buffer, [startupTime, 'ms for initial analysis to complete']); | |
| 337 } | |
| 338 buffer.write('</table>'); | |
| 339 }, (StringBuffer buffer) { | |
| 340 ServerPerformance perf = analysisServer.performanceAfterStartup; | |
| 341 if (perf == null) { | |
| 342 return; | |
| 343 } | |
| 344 int requestCount = perf.requestCount; | |
| 345 num averageLatency = requestCount > 0 | |
| 346 ? (perf.requestLatency * 10 / requestCount).round() / 10 | |
| 347 : 0; | |
| 348 int maximumLatency = perf.maxLatency; | |
| 349 num slowRequestPercent = requestCount > 0 | |
| 350 ? (perf.slowRequestCount * 100 / requestCount).round() | |
| 351 : 0; | |
| 352 buffer.write('<h4>Current</h4>'); | |
| 353 buffer.write('<table>'); | |
| 354 _writeRow(buffer, [requestCount, 'requests'], | |
| 355 classes: ["right", null]); | |
| 356 _writeRow(buffer, [averageLatency, 'ms average latency'], | |
| 357 classes: ["right", null]); | |
| 358 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | |
| 359 classes: ["right", null]); | |
| 360 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | |
| 361 classes: ["right", null]); | |
| 362 buffer.write('</table>'); | |
| 363 }); | |
| 364 }); | |
| 365 }); | |
| 366 } | |
| 367 | |
| 368 /** | |
| 369 * Return a response displaying code completion information. | |
| 370 */ | |
| 371 void _returnCompletionInfo(HttpRequest request) { | |
| 372 String value = request.requestedUri.queryParameters['index']; | |
| 373 int index = value != null ? int.parse(value, onError: (_) => 0) : 0; | |
| 374 _writeResponse(request, (StringBuffer buffer) { | |
| 375 _writePage(buffer, 'Completion Stats', [], (StringBuffer buffer) { | |
| 376 _writeCompletionPerformanceDetail(buffer, index); | |
| 377 _writeCompletionPerformanceList(buffer); | |
| 378 }); | |
| 379 }); | |
| 380 } | |
| 381 | |
| 382 /** | |
| 383 * Return a response containing information about a single source file in the | |
| 384 * cache. | |
| 385 */ | |
| 386 void _returnContextInfo(HttpRequest request) { | |
| 387 AnalysisServer analysisServer = _server.analysisServer; | |
| 388 if (analysisServer == null) { | |
| 389 return _returnFailure(request, 'Analysis server not running'); | |
| 390 } | |
| 391 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 392 if (contextFilter == null) { | |
| 393 return _returnFailure( | |
| 394 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 395 } | |
| 396 AnalysisDriver driver = null; | |
| 397 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 398 if (folder == null) { | |
| 399 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 400 } else { | |
| 401 driver = analysisServer.driverMap[folder]; | |
| 402 } | |
| 403 | |
| 404 List<String> priorityFiles = driver.priorityFiles; | |
| 405 List<String> addedFiles = driver.addedFiles.toList(); | |
| 406 List<String> implicitFiles = | |
| 407 driver.knownFiles.difference(driver.addedFiles).toList(); | |
| 408 addedFiles.sort(); | |
| 409 implicitFiles.sort(); | |
| 410 | |
| 411 // TODO(scheglov) Use file overlays. | |
| 412 // _overlayContents.clear(); | |
| 413 // context.visitContentCache((String fullName, int stamp, String contents) { | |
| 414 // _overlayContents[fullName] = contents; | |
| 415 // }); | |
| 416 | |
| 417 void _writeFiles(StringBuffer buffer, String title, List<String> files) { | |
| 418 buffer.write('<h3>$title</h3>'); | |
| 419 if (files == null || files.isEmpty) { | |
| 420 buffer.write('<p>None</p>'); | |
| 421 } else { | |
| 422 buffer.write('<p><table style="width: 100%">'); | |
| 423 for (String file in files) { | |
| 424 buffer.write('<tr><td>'); | |
| 425 buffer.write(file); | |
| 426 buffer.write('</td><td>'); | |
| 427 if (_overlayContents.containsKey(files)) { | |
| 428 buffer.write(makeLink(OVERLAY_PATH, {PATH_PARAM: file}, 'overlay')); | |
| 429 } | |
| 430 buffer.write('</td></tr>'); | |
| 431 } | |
| 432 buffer.write('</table></p>'); | |
| 433 } | |
| 434 } | |
| 435 | |
| 436 void writeOptions(StringBuffer buffer, AnalysisOptionsImpl options, | |
| 437 {void writeAdditionalOptions(StringBuffer buffer)}) { | |
| 438 if (options == null) { | |
| 439 buffer.write('<p>No option information available.</p>'); | |
| 440 return; | |
| 441 } | |
| 442 buffer.write('<p>'); | |
| 443 _writeOption( | |
| 444 buffer, 'Analyze functon bodies', options.analyzeFunctionBodies); | |
| 445 _writeOption(buffer, 'Enable asserts in initializer lists', | |
| 446 options.enableAssertInitializer); | |
| 447 _writeOption( | |
| 448 buffer, 'Enable strict call checks', options.enableStrictCallChecks); | |
| 449 _writeOption(buffer, 'Enable super mixins', options.enableSuperMixins); | |
| 450 _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint); | |
| 451 _writeOption(buffer, 'Generate errors in implicit files', | |
| 452 options.generateImplicitErrors); | |
| 453 _writeOption( | |
| 454 buffer, 'Generate errors in SDK files', options.generateSdkErrors); | |
| 455 _writeOption(buffer, 'Generate hints', options.hint); | |
| 456 _writeOption(buffer, 'Incremental resolution', options.incremental); | |
| 457 _writeOption(buffer, 'Incremental resolution with API changes', | |
| 458 options.incrementalApi); | |
| 459 _writeOption(buffer, 'Preserve comments', options.preserveComments); | |
| 460 _writeOption(buffer, 'Strong mode', options.strongMode); | |
| 461 _writeOption(buffer, 'Strong mode hints', options.strongModeHints); | |
| 462 if (writeAdditionalOptions != null) { | |
| 463 writeAdditionalOptions(buffer); | |
| 464 } | |
| 465 buffer.write('</p>'); | |
| 466 } | |
| 467 | |
| 468 _writeResponse(request, (StringBuffer buffer) { | |
| 469 String contextName = path.basename(contextFilter); | |
| 470 _writePage(buffer, 'Context: $contextName', [contextFilter], | |
| 471 (StringBuffer buffer) { | |
| 472 buffer.write('<h3>Configuration</h3>'); | |
| 473 | |
| 474 AnalysisOptions analysisOptions = driver.analysisOptions; | |
| 475 | |
| 476 _writeColumns(buffer, <HtmlGenerator>[ | |
| 477 (StringBuffer buffer) { | |
| 478 buffer.write('<p><b>Context Options</b></p>'); | |
| 479 writeOptions(buffer, analysisOptions); | |
| 480 }, | |
| 481 (StringBuffer buffer) { | |
| 482 buffer.write('<p><b>SDK Context Options</b></p>'); | |
| 483 DartSdk sdk = driver?.sourceFactory?.dartSdk; | |
| 484 writeOptions(buffer, sdk?.context?.analysisOptions, | |
| 485 writeAdditionalOptions: (StringBuffer buffer) { | |
| 486 if (sdk is FolderBasedDartSdk) { | |
| 487 _writeOption(buffer, 'Use summaries', sdk.useSummary); | |
| 488 } | |
| 489 }); | |
| 490 }, | |
| 491 (StringBuffer buffer) { | |
| 492 List<Linter> lints = analysisOptions.lintRules; | |
| 493 buffer.write('<p><b>Lints</b></p>'); | |
| 494 if (lints.isEmpty) { | |
| 495 buffer.write('<p>none</p>'); | |
| 496 } else { | |
| 497 for (Linter lint in lints) { | |
| 498 buffer.write('<p>'); | |
| 499 buffer.write(lint.runtimeType); | |
| 500 buffer.write('</p>'); | |
| 501 } | |
| 502 } | |
| 503 | |
| 504 List<ErrorProcessor> errorProcessors = | |
| 505 analysisOptions.errorProcessors; | |
| 506 int processorCount = errorProcessors?.length ?? 0; | |
| 507 buffer | |
| 508 .write('<p><b>Error Processor count</b>: $processorCount</p>'); | |
| 509 } | |
| 510 ]); | |
| 511 | |
| 512 _writeColumns(buffer, <HtmlGenerator>[ | |
| 513 (StringBuffer buffer) { | |
| 514 buffer.write('<p><b>analysis_options path</b></p>'); | |
| 515 | |
| 516 if (driver.contextRoot.optionsFilePath != null) { | |
| 517 buffer.write( | |
| 518 '<p>${HTML_ESCAPE.convert(driver.contextRoot.optionsFilePath)}
</p>'); | |
| 519 } else { | |
| 520 buffer.write('<p>none</p>'); | |
| 521 } | |
| 522 } | |
| 523 ]); | |
| 524 | |
| 525 SourceFactory sourceFactory = driver.sourceFactory; | |
| 526 if (sourceFactory is SourceFactoryImpl) { | |
| 527 buffer.write('<h3>Resolvers</h3>'); | |
| 528 for (UriResolver resolver in sourceFactory.resolvers) { | |
| 529 buffer.write('<p>'); | |
| 530 buffer.write(resolver.runtimeType); | |
| 531 if (resolver is DartUriResolver) { | |
| 532 DartSdk sdk = resolver.dartSdk; | |
| 533 buffer.write(' (sdk = '); | |
| 534 buffer.write(sdk.runtimeType); | |
| 535 if (sdk is FolderBasedDartSdk) { | |
| 536 buffer.write(' (path = '); | |
| 537 buffer.write(sdk.directory.path); | |
| 538 buffer.write(')'); | |
| 539 } else if (sdk is EmbedderSdk) { | |
| 540 buffer.write(' (map = '); | |
| 541 _writeMap(buffer, sdk.urlMappings); | |
| 542 buffer.write(')'); | |
| 543 } | |
| 544 buffer.write(')'); | |
| 545 } else if (resolver is SdkExtUriResolver) { | |
| 546 buffer.write(' (map = '); | |
| 547 _writeMap(buffer, resolver.urlMappings); | |
| 548 buffer.write(')'); | |
| 549 } else if (resolver is PackageMapUriResolver) { | |
| 550 _writeMap(buffer, resolver.packageMap, valueWriter: _writeList); | |
| 551 } | |
| 552 buffer.write('</p>'); | |
| 553 } | |
| 554 } | |
| 555 | |
| 556 _writeFiles( | |
| 557 buffer, 'Priority Files (${priorityFiles.length})', priorityFiles); | |
| 558 _writeFiles(buffer, 'Added Files (${addedFiles.length})', addedFiles); | |
| 559 _writeFiles( | |
| 560 buffer, | |
| 561 'Implicitly Analyzed Files (${implicitFiles.length})', | |
| 562 implicitFiles); | |
| 563 | |
| 564 // TODO(scheglov) Show exceptions. | |
| 565 // buffer.write('<h3>Exceptions</h3>'); | |
| 566 // if (exceptions.isEmpty) { | |
| 567 // buffer.write('<p>none</p>'); | |
| 568 // } else { | |
| 569 // exceptions.forEach((CaughtException exception) { | |
| 570 // _writeException(buffer, exception); | |
| 571 // }); | |
| 572 // } | |
| 573 }); | |
| 574 }); | |
| 575 } | |
| 576 | |
| 577 void _returnFailure(HttpRequest request, String message) { | |
| 578 _writeResponse(request, (StringBuffer buffer) { | |
| 579 _writePage(buffer, 'Failure', [], (StringBuffer buffer) { | |
| 580 buffer.write(HTML_ESCAPE.convert(message)); | |
| 581 }); | |
| 582 }); | |
| 583 } | |
| 584 | |
| 585 void _returnOverlayContents(HttpRequest request) { | |
| 586 String path = request.requestedUri.queryParameters[PATH_PARAM]; | |
| 587 if (path == null) { | |
| 588 return _returnFailure(request, 'Query parameter $PATH_PARAM required'); | |
| 589 } | |
| 590 String contents = _overlayContents[path]; | |
| 591 | |
| 592 _writeResponse(request, (StringBuffer buffer) { | |
| 593 _writePage(buffer, 'Overlay', [], (StringBuffer buffer) { | |
| 594 buffer.write('<pre>${HTML_ESCAPE.convert(contents)}</pre>'); | |
| 595 }); | |
| 596 }); | |
| 597 } | |
| 598 | |
| 599 /** | |
| 600 * Return a response displaying overlays information. | |
| 601 */ | |
| 602 void _returnOverlaysInfo(HttpRequest request) { | |
| 603 AnalysisServer analysisServer = _server.analysisServer; | |
| 604 if (analysisServer == null) { | |
| 605 return _returnFailure(request, 'Analysis server is not running'); | |
| 606 } | |
| 607 | |
| 608 _writeResponse(request, (StringBuffer buffer) { | |
| 609 _writePage(buffer, 'Overlay information', [], (StringBuffer buffer) { | |
| 610 buffer.write('<table border="1">'); | |
| 611 _overlayContents.clear(); | |
| 612 ContentCache overlayState = analysisServer.overlayState; | |
| 613 overlayState.accept((String fullName, int stamp, String contents) { | |
| 614 buffer.write('<tr>'); | |
| 615 String link = | |
| 616 makeLink(OVERLAY_PATH, {PATH_PARAM: fullName}, fullName); | |
| 617 DateTime time = new DateTime.fromMillisecondsSinceEpoch(stamp); | |
| 618 _writeRow(buffer, [link, time]); | |
| 619 _overlayContents[fullName] = contents; | |
| 620 }); | |
| 621 int count = _overlayContents.length; | |
| 622 buffer.write('<tr><td colspan="2">Total: $count entries</td></tr>'); | |
| 623 buffer.write('</table>'); | |
| 624 }); | |
| 625 }); | |
| 626 } | |
| 627 | |
| 628 void _returnRedirect(HttpRequest request, String pathFragment) { | |
| 629 HttpResponse response = request.response; | |
| 630 response.redirect(request.uri.resolve(pathFragment)); | |
| 631 } | |
| 632 | |
| 633 /** | |
| 634 * Return a response indicating the status of the analysis server. | |
| 635 */ | |
| 636 void _returnServerStatus(HttpRequest request) { | |
| 637 _writeResponse(request, (StringBuffer buffer) { | |
| 638 _writePage(buffer, 'Status', [], (StringBuffer buffer) { | |
| 639 if (_writeServerStatus(buffer)) { | |
| 640 _writeAnalysisStatus(buffer); | |
| 641 _writeEditStatus(buffer); | |
| 642 _writeExecutionStatus(buffer); | |
| 643 _writePluginStatus(buffer); | |
| 644 _writeRecentOutput(buffer); | |
| 645 } | |
| 646 }); | |
| 647 }); | |
| 648 } | |
| 649 | |
| 650 /** | |
| 651 * Return an error in response to an unrecognized request received by the HTTP | |
| 652 * server. | |
| 653 */ | |
| 654 void _returnUnknownRequest(HttpRequest request) { | |
| 655 _writeResponse(request, (StringBuffer buffer) { | |
| 656 _writePage(buffer, 'Analysis Server', [], (StringBuffer buffer) { | |
| 657 buffer.write('<h3>Unknown page: '); | |
| 658 buffer.write(request.uri.path); | |
| 659 buffer.write('</h3>'); | |
| 660 buffer.write(''' | |
| 661 <p> | |
| 662 You have reached an un-recognized page. If you reached this page by | |
| 663 following a link from a status page, please report the broken link to | |
| 664 the Dart analyzer team: | |
| 665 <a>https://github.com/dart-lang/sdk/issues/new</a>. | |
| 666 </p><p> | |
| 667 If you mistyped the URL, you can correct it or return to | |
| 668 ${makeLink(STATUS_PATH, {}, 'the main status page')}. | |
| 669 </p>'''); | |
| 670 }); | |
| 671 }); | |
| 672 } | |
| 673 | |
| 674 /** | |
| 675 * Return a two digit decimal representation of the given non-negative integer | |
| 676 * [value]. | |
| 677 */ | |
| 678 String _twoDigit(int value) => value.toString().padLeft(2, '0'); | |
| 679 | |
| 680 /** | |
| 681 * Write the status of the analysis domain (on the main status page) to the | |
| 682 * given [buffer] object. | |
| 683 */ | |
| 684 void _writeAnalysisStatus(StringBuffer buffer) { | |
| 685 AnalysisServer analysisServer = _server.analysisServer; | |
| 686 Map<Folder, AnalysisDriver> driverMap = analysisServer.driverMap; | |
| 687 List<Folder> folders = driverMap.keys.toList(); | |
| 688 folders.sort((Folder first, Folder second) => | |
| 689 first.shortName.compareTo(second.shortName)); | |
| 690 | |
| 691 buffer.write('<h3>Analysis Domain</h3>'); | |
| 692 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 693 buffer.write('<p>Using package resolver provider: '); | |
| 694 buffer.write(_server.packageResolverProvider != null); | |
| 695 buffer.write('</p>'); | |
| 696 buffer.write(makeLink(OVERLAYS_PATH, {}, 'Overlay information')); | |
| 697 | |
| 698 buffer.write('<p><b>Analysis Contexts</b></p>'); | |
| 699 bool first = true; | |
| 700 folders.forEach((Folder folder) { | |
| 701 if (first) { | |
| 702 first = false; | |
| 703 } else { | |
| 704 buffer.write('<br>'); | |
| 705 } | |
| 706 String key = folder.shortName; | |
| 707 buffer.write(makeLink(CONTEXT_PATH, {CONTEXT_QUERY_PARAM: folder.path}, | |
| 708 key, _hasException(driverMap[folder]))); | |
| 709 if (!folder.getChild('.packages').exists) { | |
| 710 buffer.write(' [no .packages file]'); | |
| 711 } | |
| 712 }); | |
| 713 | |
| 714 int freq = AnalysisServer.performOperationDelayFrequency; | |
| 715 String delay = freq > 0 ? '1 ms every $freq ms' : 'off'; | |
| 716 | |
| 717 buffer.write('<p><b>Performance Data</b></p>'); | |
| 718 buffer.write( | |
| 719 makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Analysis performance')); | |
| 720 buffer.write('<br>'); | |
| 721 buffer.write('Perform operation delay: $delay'); | |
| 722 buffer.write('<br>'); | |
| 723 }, (StringBuffer buffer) { | |
| 724 _writeSubscriptionMap( | |
| 725 buffer, AnalysisService.VALUES, analysisServer.analysisServices); | |
| 726 }); | |
| 727 } | |
| 728 | |
| 729 /** | |
| 730 * Write multiple columns of information to the given [buffer], where the list | |
| 731 * of [columns] functions are used to generate the content of those columns. | |
| 732 */ | |
| 733 void _writeColumns(StringBuffer buffer, List<HtmlGenerator> columns) { | |
| 734 buffer | |
| 735 .write('<table class="column"><tr class="column"><td class="column">'); | |
| 736 int count = columns.length; | |
| 737 for (int i = 0; i < count; i++) { | |
| 738 if (i > 0) { | |
| 739 buffer.write('</td><td class="column">'); | |
| 740 } | |
| 741 columns[i](buffer); | |
| 742 } | |
| 743 buffer.write('</td></tr></table>'); | |
| 744 } | |
| 745 | |
| 746 /** | |
| 747 * Write performance information about a specific completion request | |
| 748 * to the given [buffer] object. | |
| 749 */ | |
| 750 void _writeCompletionPerformanceDetail(StringBuffer buffer, int index) { | |
| 751 CompletionDomainHandler handler = _completionDomainHandler; | |
| 752 CompletionPerformance performance; | |
| 753 if (handler != null) { | |
| 754 List<CompletionPerformance> list = handler.performanceList; | |
| 755 if (list != null && list.isNotEmpty) { | |
| 756 performance = list[max(0, min(list.length - 1, index))]; | |
| 757 } | |
| 758 } | |
| 759 if (performance == null) { | |
| 760 buffer.write('<h3>Completion Performance Detail</h3>'); | |
| 761 buffer.write('<p>No completions yet</p>'); | |
| 762 return; | |
| 763 } | |
| 764 buffer.write('<h3>Completion Performance Detail</h3>'); | |
| 765 buffer.write('<p>${performance.startTimeAndMs} for ${performance.source}'); | |
| 766 buffer.write('<table>'); | |
| 767 _writeRow(buffer, ['Elapsed', '', 'Operation'], header: true); | |
| 768 performance.operations.forEach((OperationPerformance op) { | |
| 769 String elapsed = op.elapsed != null ? op.elapsed.toString() : '???'; | |
| 770 _writeRow(buffer, [elapsed, ' ', op.name]); | |
| 771 }); | |
| 772 buffer.write('</table>'); | |
| 773 buffer.write('<p><b>Compute Cache Performance</b>: '); | |
| 774 if (handler.computeCachePerformance == null) { | |
| 775 buffer.write('none'); | |
| 776 } else { | |
| 777 int elapsed = handler.computeCachePerformance.elapsedInMilliseconds; | |
| 778 Source source = handler.computeCachePerformance.source; | |
| 779 buffer.write(' $elapsed ms for $source'); | |
| 780 } | |
| 781 buffer.write('</p>'); | |
| 782 } | |
| 783 | |
| 784 /** | |
| 785 * Write a table showing summary information for the last several | |
| 786 * completion requests to the given [buffer] object. | |
| 787 */ | |
| 788 void _writeCompletionPerformanceList(StringBuffer buffer) { | |
| 789 CompletionDomainHandler handler = _completionDomainHandler; | |
| 790 buffer.write('<h3>Completion Performance List</h3>'); | |
| 791 if (handler == null) { | |
| 792 return; | |
| 793 } | |
| 794 buffer.write('<table>'); | |
| 795 _writeRow( | |
| 796 buffer, | |
| 797 [ | |
| 798 'Start Time', | |
| 799 '', | |
| 800 'First (ms)', | |
| 801 '', | |
| 802 'Complete (ms)', | |
| 803 '', | |
| 804 '# Notifications', | |
| 805 '', | |
| 806 '# Suggestions', | |
| 807 '', | |
| 808 'Snippet' | |
| 809 ], | |
| 810 header: true); | |
| 811 int index = 0; | |
| 812 for (CompletionPerformance performance in handler.performanceList) { | |
| 813 String link = makeLink(COMPLETION_PATH, {'index': '$index'}, | |
| 814 '${performance.startTimeAndMs}'); | |
| 815 _writeRow(buffer, [ | |
| 816 link, | |
| 817 ' ', | |
| 818 performance.firstNotificationInMilliseconds, | |
| 819 ' ', | |
| 820 performance.elapsedInMilliseconds, | |
| 821 ' ', | |
| 822 performance.notificationCount, | |
| 823 ' ', | |
| 824 performance.suggestionCount, | |
| 825 ' ', | |
| 826 HTML_ESCAPE.convert(performance.snippet) | |
| 827 ]); | |
| 828 ++index; | |
| 829 } | |
| 830 | |
| 831 buffer.write('</table>'); | |
| 832 buffer.write(''' | |
| 833 <p><strong>First (ms)</strong> - the number of milliseconds | |
| 834 from when completion received the request until the first notification | |
| 835 with completion results was queued for sending back to the client. | |
| 836 <p><strong>Complete (ms)</strong> - the number of milliseconds | |
| 837 from when completion received the request until the final notification | |
| 838 with completion results was queued for sending back to the client. | |
| 839 <p><strong># Notifications</strong> - the total number of notifications | |
| 840 sent to the client with completion results for this request. | |
| 841 <p><strong># Suggestions</strong> - the number of suggestions | |
| 842 sent to the client in the first notification, followed by a comma, | |
| 843 followed by the number of suggestions send to the client | |
| 844 in the last notification. If there is only one notification, | |
| 845 then there will be only one number in this column.'''); | |
| 846 } | |
| 847 | |
| 848 /** | |
| 849 * Write the status of the edit domain (on the main status page) to the given | |
| 850 * [buffer]. | |
| 851 */ | |
| 852 void _writeEditStatus(StringBuffer buffer) { | |
| 853 buffer.write('<h3>Edit Domain</h3>'); | |
| 854 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 855 buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion stats')); | |
| 856 }, (StringBuffer buffer) {}); | |
| 857 } | |
| 858 | |
| 859 /** | |
| 860 * Write a representation of the given [caughtException] to the given | |
| 861 * [buffer]. If [isCause] is `true`, then the exception was a cause for | |
| 862 * another exception. | |
| 863 */ | |
| 864 void _writeException(StringBuffer buffer, CaughtException caughtException, | |
| 865 {bool isCause: false}) { | |
| 866 Object exception = caughtException.exception; | |
| 867 | |
| 868 if (exception is AnalysisException) { | |
| 869 buffer.write('<p>'); | |
| 870 if (isCause) { | |
| 871 buffer.write('Caused by '); | |
| 872 } | |
| 873 buffer.write(exception.message); | |
| 874 buffer.write('</p>'); | |
| 875 _writeStackTrace(buffer, caughtException.stackTrace); | |
| 876 CaughtException cause = exception.cause; | |
| 877 if (cause != null) { | |
| 878 buffer.write('<blockquote>'); | |
| 879 _writeException(buffer, cause, isCause: true); | |
| 880 buffer.write('</blockquote>'); | |
| 881 } | |
| 882 } else { | |
| 883 buffer.write('<p>'); | |
| 884 if (isCause) { | |
| 885 buffer.write('Caused by '); | |
| 886 } | |
| 887 buffer.write(exception.toString()); | |
| 888 buffer.write('<p>'); | |
| 889 _writeStackTrace(buffer, caughtException.stackTrace); | |
| 890 } | |
| 891 } | |
| 892 | |
| 893 /** | |
| 894 * Write the status of the execution domain (on the main status page) to the | |
| 895 * given [buffer]. | |
| 896 */ | |
| 897 void _writeExecutionStatus(StringBuffer buffer) { | |
| 898 AnalysisServer analysisServer = _server.analysisServer; | |
| 899 ExecutionDomainHandler handler = analysisServer.handlers.firstWhere( | |
| 900 (RequestHandler handler) => handler is ExecutionDomainHandler, | |
| 901 orElse: () => null); | |
| 902 Set<ExecutionService> services = new Set<ExecutionService>(); | |
| 903 if (handler.onFileAnalyzed != null) { | |
| 904 services.add(ExecutionService.LAUNCH_DATA); | |
| 905 } | |
| 906 | |
| 907 if (handler != null) { | |
| 908 buffer.write('<h3>Execution Domain</h3>'); | |
| 909 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 910 buffer.write('<br>'); | |
| 911 }, (StringBuffer buffer) { | |
| 912 _writeSubscriptionList(buffer, ExecutionService.VALUES, services); | |
| 913 }); | |
| 914 } | |
| 915 } | |
| 916 | |
| 917 /** | |
| 918 * Write to the given [buffer] a representation of the given [list]. | |
| 919 */ | |
| 920 void _writeList<E>(StringBuffer buffer, List<E> list, | |
| 921 {void elementWriter(StringBuffer buffer, E element)}) { | |
| 922 buffer.write('['); | |
| 923 for (int i = 0; i < list.length; i++) { | |
| 924 if (i > 0) { | |
| 925 buffer.write(', '); | |
| 926 } | |
| 927 if (elementWriter == null) { | |
| 928 buffer.write(list[i]); | |
| 929 } else { | |
| 930 elementWriter(buffer, list[i]); | |
| 931 } | |
| 932 } | |
| 933 buffer.write(']'); | |
| 934 } | |
| 935 | |
| 936 /** | |
| 937 * Write to the given [buffer] a representation of the given [map]. | |
| 938 */ | |
| 939 void _writeMap<V>(StringBuffer buffer, Map<String, V> map, | |
| 940 {void valueWriter(StringBuffer buffer, V value)}) { | |
| 941 List<String> keys = map.keys.toList(); | |
| 942 keys.sort(); | |
| 943 int length = keys.length; | |
| 944 buffer.write('{'); | |
| 945 for (int i = 0; i < length; i++) { | |
| 946 buffer.write('<br>'); | |
| 947 String key = keys[i]; | |
| 948 V value = map[key]; | |
| 949 if (i > 0) { | |
| 950 buffer.write(', '); | |
| 951 } | |
| 952 buffer.write(key); | |
| 953 buffer.write(' = '); | |
| 954 if (valueWriter == null) { | |
| 955 buffer.write(value); | |
| 956 } else { | |
| 957 valueWriter(buffer, value); | |
| 958 } | |
| 959 } | |
| 960 buffer.write('<br>}'); | |
| 961 } | |
| 962 | |
| 963 /** | |
| 964 * Write a representation of an analysis option with the given [name] and | |
| 965 * [value] to the given [buffer]. The option should be separated from other | |
| 966 * options unless the [last] flag is true, indicating that this is the last | |
| 967 * option in the list of options. | |
| 968 */ | |
| 969 void _writeOption(StringBuffer buffer, String name, Object value, | |
| 970 {bool last: false}) { | |
| 971 buffer.write(name); | |
| 972 buffer.write(' = '); | |
| 973 buffer.write(value.toString()); | |
| 974 if (!last) { | |
| 975 buffer.write('<br>'); | |
| 976 } | |
| 977 } | |
| 978 | |
| 979 /** | |
| 980 * Write a standard HTML page to the given [buffer]. The page will have the | |
| 981 * given [title] and a body that is generated by the given [body] generator. | |
| 982 */ | |
| 983 void _writePage(StringBuffer buffer, String title, List<String> subtitles, | |
| 984 HtmlGenerator body) { | |
| 985 DateTime now = new DateTime.now(); | |
| 986 String date = "${now.month}/${now.day}/${now.year}"; | |
| 987 String time = | |
| 988 "${now.hour}:${_twoDigit(now.minute)}:${_twoDigit(now.second)}.${now.mil
lisecond}"; | |
| 989 | |
| 990 buffer.write('<!DOCTYPE html>'); | |
| 991 buffer.write('<html>'); | |
| 992 buffer.write('<head>'); | |
| 993 buffer.write('<meta charset="utf-8">'); | |
| 994 buffer.write( | |
| 995 '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
); | |
| 996 buffer.write('<title>Analysis Server</title>'); | |
| 997 buffer.write('<style>'); | |
| 998 buffer.write('a {color: #0000DD; text-decoration: none;}'); | |
| 999 buffer.write('a:link.error {background-color: #FFEEEE;}'); | |
| 1000 buffer.write('a:visited.error {background-color: #FFEEEE;}'); | |
| 1001 buffer.write('a:hover.error {background-color: #FFEEEE;}'); | |
| 1002 buffer.write('a:active.error {background-color: #FFEEEE;}'); | |
| 1003 buffer.write( | |
| 1004 'div.subtitle {float: right; font-weight: normal; font-size: 1rem;}'); | |
| 1005 buffer.write('h2 {margin-top: 0;}'); | |
| 1006 buffer.write('h3 {border-bottom: 1px #DDD solid; margin-bottom: 0em;}'); | |
| 1007 buffer.write('p {margin-top: 0.5em; margin-bottom: 0;}'); | |
| 1008 buffer.write( | |
| 1009 'p.commentary {margin-top: 1em; margin-bottom: 1em; margin-left: 2em; fo
nt-style: italic;}'); | |
| 1010 // response.write('span.error {text-decoration-line: underline; text-decorati
on-color: red; text-decoration-style: wavy;}'); | |
| 1011 buffer.write( | |
| 1012 'table.column {border: 0px solid black; width: 100%; table-layout: fixed
;}'); | |
| 1013 buffer.write('td.column {vertical-align: top; width: 50%;}'); | |
| 1014 buffer.write('td.right {text-align: right;}'); | |
| 1015 buffer.write('th {text-align: left; vertical-align:top;}'); | |
| 1016 buffer.write('tr {vertical-align:top;}'); | |
| 1017 buffer.write('</style>'); | |
| 1018 buffer.write('</head>'); | |
| 1019 | |
| 1020 buffer.write('<body>'); | |
| 1021 buffer.write('<h2>$title <div class="subtitle">$date, $time</div></h2>'); | |
| 1022 if (subtitles != null && subtitles.isNotEmpty) { | |
| 1023 bool first = true; | |
| 1024 for (String subtitle in subtitles) { | |
| 1025 if (first) { | |
| 1026 first = false; | |
| 1027 } else { | |
| 1028 buffer.write('<br>'); | |
| 1029 } | |
| 1030 buffer.write('<b>'); | |
| 1031 buffer.write(subtitle); | |
| 1032 buffer.write('</b>'); | |
| 1033 } | |
| 1034 } | |
| 1035 try { | |
| 1036 body(buffer); | |
| 1037 } catch (exception, stackTrace) { | |
| 1038 buffer.write('<h3>Exception while creating page</h3>'); | |
| 1039 _writeException(buffer, new CaughtException(exception, stackTrace)); | |
| 1040 } | |
| 1041 buffer.write('</body>'); | |
| 1042 buffer.write('</html>'); | |
| 1043 } | |
| 1044 | |
| 1045 /** | |
| 1046 * Write the recent output section (on the main status page) to the given | |
| 1047 * [buffer] object. | |
| 1048 */ | |
| 1049 void _writePluginStatus(StringBuffer buffer) { | |
| 1050 void writePlugin(Plugin plugin) { | |
| 1051 buffer.write(plugin.uniqueIdentifier); | |
| 1052 buffer.write(' ('); | |
| 1053 buffer.write(plugin.runtimeType); | |
| 1054 buffer.write(')<br>'); | |
| 1055 } | |
| 1056 | |
| 1057 buffer.write('<h3>Plugin Status</h3><p>'); | |
| 1058 writePlugin(AnalysisEngine.instance.enginePlugin); | |
| 1059 writePlugin(_server.serverPlugin); | |
| 1060 for (Plugin plugin in _server.analysisServer.userDefinedPlugins) { | |
| 1061 writePlugin(plugin); | |
| 1062 } | |
| 1063 buffer.write('<p>'); | |
| 1064 } | |
| 1065 | |
| 1066 /** | |
| 1067 * Write the recent output section (on the main status page) to the given | |
| 1068 * [buffer] object. | |
| 1069 */ | |
| 1070 void _writeRecentOutput(StringBuffer buffer) { | |
| 1071 buffer.write('<h3>Recent Output</h3>'); | |
| 1072 String output = HTML_ESCAPE.convert(_printBuffer.join('\n')); | |
| 1073 if (output.isEmpty) { | |
| 1074 buffer.write('<i>none</i>'); | |
| 1075 } else { | |
| 1076 buffer.write('<pre>'); | |
| 1077 buffer.write(output); | |
| 1078 buffer.write('</pre>'); | |
| 1079 } | |
| 1080 } | |
| 1081 | |
| 1082 void _writeResponse(HttpRequest request, HtmlGenerator writePage) { | |
| 1083 HttpResponse response = request.response; | |
| 1084 response.statusCode = HttpStatus.OK; | |
| 1085 response.headers.contentType = _htmlContent; | |
| 1086 try { | |
| 1087 StringBuffer buffer = new StringBuffer(); | |
| 1088 try { | |
| 1089 writePage(buffer); | |
| 1090 } catch (exception, stackTrace) { | |
| 1091 buffer.clear(); | |
| 1092 _writePage(buffer, 'Internal Exception', [], (StringBuffer buffer) { | |
| 1093 _writeException(buffer, new CaughtException(exception, stackTrace)); | |
| 1094 }); | |
| 1095 } | |
| 1096 response.write(buffer.toString()); | |
| 1097 } finally { | |
| 1098 response.close(); | |
| 1099 } | |
| 1100 } | |
| 1101 | |
| 1102 /** | |
| 1103 * Write a single row within a table to the given [buffer]. The row will have | |
| 1104 * one cell for each of the [columns], and will be a header row if [header] is | |
| 1105 * `true`. | |
| 1106 */ | |
| 1107 void _writeRow(StringBuffer buffer, List<Object> columns, | |
| 1108 {bool header: false, List<String> classes}) { | |
| 1109 buffer.write('<tr>'); | |
| 1110 int count = columns.length; | |
| 1111 int maxClassIndex = classes == null ? 0 : classes.length - 1; | |
| 1112 for (int i = 0; i < count; i++) { | |
| 1113 String classAttribute = ''; | |
| 1114 if (classes != null) { | |
| 1115 String className = classes[min(i, maxClassIndex)]; | |
| 1116 if (className != null) { | |
| 1117 classAttribute = ' class="$className"'; | |
| 1118 } | |
| 1119 } | |
| 1120 if (header) { | |
| 1121 buffer.write('<th$classAttribute>'); | |
| 1122 } else { | |
| 1123 buffer.write('<td$classAttribute>'); | |
| 1124 } | |
| 1125 if (columns[i] is int) { | |
| 1126 buffer.write(_writeWithSeparators(columns[i])); | |
| 1127 } else { | |
| 1128 buffer.write(columns[i]); | |
| 1129 } | |
| 1130 if (header) { | |
| 1131 buffer.write('</th>'); | |
| 1132 } else { | |
| 1133 buffer.write('</td>'); | |
| 1134 } | |
| 1135 } | |
| 1136 buffer.write('</tr>'); | |
| 1137 } | |
| 1138 | |
| 1139 /** | |
| 1140 * Write the status of the service domain (on the main status page) to the | |
| 1141 * given [response] object. | |
| 1142 */ | |
| 1143 bool _writeServerStatus(StringBuffer buffer) { | |
| 1144 AnalysisServer analysisServer = _server.analysisServer; | |
| 1145 Set<ServerService> services = analysisServer.serverServices; | |
| 1146 | |
| 1147 buffer.write('<h3>Server Domain</h3>'); | |
| 1148 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 1149 buffer.write('<p><b>State</b></p>'); | |
| 1150 if (analysisServer == null) { | |
| 1151 buffer.write('Status: <span style="color:red">Not running</span>'); | |
| 1152 return; | |
| 1153 } | |
| 1154 buffer.write('Status: Running<br>'); | |
| 1155 buffer.write('New analysis driver: '); | |
| 1156 buffer.write(analysisServer.options.enableNewAnalysisDriver); | |
| 1157 buffer.write('<br>'); | |
| 1158 buffer.write('Instrumentation: '); | |
| 1159 if (AnalysisEngine.instance.instrumentationService.isActive) { | |
| 1160 buffer.write('<strong>active</strong>'); | |
| 1161 } else { | |
| 1162 buffer.write('inactive'); | |
| 1163 } | |
| 1164 buffer.write('<br>'); | |
| 1165 buffer.write('Process ID: '); | |
| 1166 buffer.write(pid); | |
| 1167 | |
| 1168 buffer.write('<p><b>Performance Data</b></p>'); | |
| 1169 buffer.write(makeLink( | |
| 1170 COMMUNICATION_PERFORMANCE_PATH, {}, 'Communication performance')); | |
| 1171 | |
| 1172 if (AnalysisEngine.instance.instrumentationService.isActive) { | |
| 1173 buffer.write('<p><b>Instrumentation</b></p>'); | |
| 1174 InstrumentationServer instrumentationServer = AnalysisEngine | |
| 1175 .instance.instrumentationService.instrumentationServer; | |
| 1176 String description = instrumentationServer.describe; | |
| 1177 HtmlEscape htmlEscape = new HtmlEscape(HtmlEscapeMode.ELEMENT); | |
| 1178 description = htmlEscape.convert(description); | |
| 1179 // Convert http(s): references to hyperlinks. | |
| 1180 final RegExp urlRegExp = new RegExp(r'[http|https]+:\/*(\S+)'); | |
| 1181 description = description.replaceAllMapped(urlRegExp, (Match match) { | |
| 1182 return '<a href="${match.group(0)}">${match.group(1)}</a>'; | |
| 1183 }); | |
| 1184 buffer.write(description.replaceAll('\n', '<br>')); | |
| 1185 } | |
| 1186 }, (StringBuffer buffer) { | |
| 1187 _writeSubscriptionList(buffer, ServerService.VALUES, services); | |
| 1188 buffer.write('<p><b>Versions</b></p>'); | |
| 1189 buffer.write('Dart SDK: ${Platform.version}<br>'); | |
| 1190 buffer.write('Analysis server version: ${AnalysisServer.VERSION}<br>'); | |
| 1191 buffer.write('<p><b>Plugins</b></p>'); | |
| 1192 List<PluginInfo> plugins = analysisServer.pluginManager.plugins; | |
| 1193 if (plugins.isEmpty) { | |
| 1194 buffer.write('none<br>'); | |
| 1195 } else { | |
| 1196 plugins.sort( | |
| 1197 (first, second) => first.data.name.compareTo(second.data.name)); | |
| 1198 for (PluginInfo plugin in plugins) { | |
| 1199 buffer.write(plugin.data.name); | |
| 1200 buffer.write(' ('); | |
| 1201 buffer.write(plugin.data.pluginId); | |
| 1202 buffer.write(', '); | |
| 1203 buffer.write(plugin.data.version); | |
| 1204 buffer.write(')<br>'); | |
| 1205 } | |
| 1206 } | |
| 1207 }); | |
| 1208 return analysisServer != null; | |
| 1209 } | |
| 1210 | |
| 1211 /** | |
| 1212 * Write a representation of the given [stackTrace] to the given [buffer]. | |
| 1213 */ | |
| 1214 void _writeStackTrace(StringBuffer buffer, StackTrace stackTrace) { | |
| 1215 if (stackTrace != null) { | |
| 1216 String trace = stackTrace.toString().replaceAll('#', '<br>#'); | |
| 1217 if (trace.startsWith('<br>#')) { | |
| 1218 trace = trace.substring(4); | |
| 1219 } | |
| 1220 buffer.write('<p>'); | |
| 1221 buffer.write(trace); | |
| 1222 buffer.write('</p>'); | |
| 1223 } | |
| 1224 } | |
| 1225 | |
| 1226 /** | |
| 1227 * Given a [service] that could be subscribed to and a set of the services | |
| 1228 * that are actually subscribed to ([subscribedServices]), write a | |
| 1229 * representation of the service to the given [buffer]. | |
| 1230 */ | |
| 1231 void _writeSubscriptionInList( | |
| 1232 StringBuffer buffer, Enum service, Set<Enum> subscribedServices) { | |
| 1233 if (subscribedServices.contains(service)) { | |
| 1234 buffer.write('<code>+ </code>'); | |
| 1235 } else { | |
| 1236 buffer.write('<code>- </code>'); | |
| 1237 } | |
| 1238 buffer.write(service.name); | |
| 1239 buffer.write('<br>'); | |
| 1240 } | |
| 1241 | |
| 1242 /** | |
| 1243 * Given a [service] that could be subscribed to and a set of paths that are | |
| 1244 * subscribed to the services ([subscribedPaths]), write a representation of | |
| 1245 * the service to the given [buffer]. | |
| 1246 */ | |
| 1247 void _writeSubscriptionInMap( | |
| 1248 StringBuffer buffer, Enum service, Set<String> subscribedPaths) { | |
| 1249 buffer.write('<p>'); | |
| 1250 buffer.write(service.name); | |
| 1251 buffer.write('<br>'); | |
| 1252 if (subscribedPaths != null && subscribedPaths.isNotEmpty) { | |
| 1253 List<String> paths = subscribedPaths.toList(); | |
| 1254 paths.sort(); | |
| 1255 for (String path in paths) { | |
| 1256 buffer.write('$path<br>'); | |
| 1257 } | |
| 1258 } | |
| 1259 buffer.write('</p>'); | |
| 1260 } | |
| 1261 | |
| 1262 /** | |
| 1263 * Given a list containing all of the services that can be subscribed to in a | |
| 1264 * single domain ([allServices]) and a set of the services that are actually | |
| 1265 * subscribed to ([subscribedServices]), write a representation of the | |
| 1266 * subscriptions to the given [buffer]. | |
| 1267 */ | |
| 1268 void _writeSubscriptionList(StringBuffer buffer, List<Enum> allServices, | |
| 1269 Set<Enum> subscribedServices) { | |
| 1270 buffer.write('<p><b>Subscriptions</b></p>'); | |
| 1271 for (Enum service in allServices) { | |
| 1272 _writeSubscriptionInList(buffer, service, subscribedServices); | |
| 1273 buffer.write('<br>'); | |
| 1274 } | |
| 1275 } | |
| 1276 | |
| 1277 /** | |
| 1278 * Given a list containing all of the services that can be subscribed to in a | |
| 1279 * single domain ([allServices]) and a set of the services that are actually | |
| 1280 * subscribed to ([subscribedServices]), write a representation of the | |
| 1281 * subscriptions to the given [buffer]. | |
| 1282 */ | |
| 1283 void _writeSubscriptionMap(StringBuffer buffer, List<Enum> allServices, | |
| 1284 Map<Enum, Set<String>> subscribedServices) { | |
| 1285 buffer.write('<p><b>Subscriptions</b></p>'); | |
| 1286 for (Enum service in allServices) { | |
| 1287 _writeSubscriptionInMap(buffer, service, subscribedServices[service]); | |
| 1288 } | |
| 1289 } | |
| 1290 | |
| 1291 /** | |
| 1292 * Write two columns of information to the given [buffer], where the | |
| 1293 * [leftColumn] and [rightColumn] functions are used to generate the content | |
| 1294 * of those columns. | |
| 1295 */ | |
| 1296 void _writeTwoColumns(StringBuffer buffer, HtmlGenerator leftColumn, | |
| 1297 HtmlGenerator rightColumn) { | |
| 1298 buffer | |
| 1299 .write('<table class="column"><tr class="column"><td class="column">'); | |
| 1300 leftColumn(buffer); | |
| 1301 buffer.write('</td><td class="column">'); | |
| 1302 rightColumn(buffer); | |
| 1303 buffer.write('</td></tr></table>'); | |
| 1304 } | |
| 1305 | |
| 1306 /** | |
| 1307 * Create a link to [path] with query parameters [params], with inner HTML | |
| 1308 * [innerHtml]. If [hasError] is `true`, then the link will have the class | |
| 1309 * 'error'. | |
| 1310 */ | |
| 1311 static String makeLink( | |
| 1312 String path, Map<String, String> params, String innerHtml, | |
| 1313 [bool hasError = false]) { | |
| 1314 Uri uri = params.isEmpty | |
| 1315 ? new Uri(path: path) | |
| 1316 : new Uri(path: path, queryParameters: params); | |
| 1317 String href = HTML_ESCAPE.convert(uri.toString()); | |
| 1318 String classAttribute = hasError ? ' class="error"' : ''; | |
| 1319 return '<a href="$href" $classAttribute>$innerHtml</a>'; | |
| 1320 } | |
| 1321 } | |
| OLD | NEW |