| 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 import 'dart:collection'; | |
| 6 import 'dart:convert'; | |
| 7 import 'dart:io'; | |
| 8 import 'dart:math'; | |
| 9 | |
| 10 import 'package:analysis_server/protocol/protocol.dart'; | |
| 11 import 'package:analysis_server/protocol/protocol_generated.dart'; | |
| 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/operation/operation.dart'; | |
| 17 import 'package:analysis_server/src/operation/operation_analysis.dart'; | |
| 18 import 'package:analysis_server/src/operation/operation_queue.dart'; | |
| 19 import 'package:analysis_server/src/server/http_server.dart'; | |
| 20 import 'package:analysis_server/src/services/completion/completion_performance.d
art'; | |
| 21 import 'package:analysis_server/src/socket_server.dart'; | |
| 22 import 'package:analysis_server/src/status/ast_writer.dart'; | |
| 23 import 'package:analysis_server/src/status/element_writer.dart'; | |
| 24 import 'package:analysis_server/src/status/memory_use.dart'; | |
| 25 import 'package:analysis_server/src/status/validator.dart'; | |
| 26 import 'package:analysis_server/src/utilities/average.dart'; | |
| 27 import 'package:analyzer/dart/ast/ast.dart'; | |
| 28 import 'package:analyzer/dart/element/element.dart'; | |
| 29 import 'package:analyzer/dart/element/visitor.dart'; | |
| 30 import 'package:analyzer/exception/exception.dart'; | |
| 31 import 'package:analyzer/file_system/file_system.dart'; | |
| 32 import 'package:analyzer/source/error_processor.dart'; | |
| 33 import 'package:analyzer/source/sdk_ext.dart'; | |
| 34 import 'package:analyzer/src/context/cache.dart'; | |
| 35 import 'package:analyzer/src/context/context.dart' show AnalysisContextImpl; | |
| 36 import 'package:analyzer/src/context/source.dart'; | |
| 37 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
| 38 import 'package:analyzer/src/generated/engine.dart'; | |
| 39 import 'package:analyzer/src/generated/resolver.dart'; | |
| 40 import 'package:analyzer/src/generated/sdk.dart'; | |
| 41 import 'package:analyzer/src/generated/source.dart'; | |
| 42 import 'package:analyzer/src/generated/utilities_collection.dart'; | |
| 43 import 'package:analyzer/src/generated/utilities_general.dart'; | |
| 44 import 'package:analyzer/src/services/lint.dart'; | |
| 45 import 'package:analyzer/src/task/dart.dart'; | |
| 46 import 'package:analyzer/src/task/driver.dart'; | |
| 47 import 'package:analyzer/src/task/html.dart'; | |
| 48 import 'package:analyzer/src/task/options.dart'; | |
| 49 import 'package:analyzer/task/dart.dart'; | |
| 50 import 'package:analyzer/task/general.dart'; | |
| 51 import 'package:analyzer/task/html.dart'; | |
| 52 import 'package:analyzer/task/model.dart'; | |
| 53 import 'package:plugin/plugin.dart'; | |
| 54 | |
| 55 /** | |
| 56 * A function that can be used to generate HTML output into the given [buffer]. | |
| 57 * The HTML that is generated must be valid (special characters must already be | |
| 58 * encoded). | |
| 59 */ | |
| 60 typedef void HtmlGenerator(StringBuffer buffer); | |
| 61 | |
| 62 class ElementCounter extends RecursiveElementVisitor { | |
| 63 Map<Type, int> counts = new HashMap<Type, int>(); | |
| 64 int elementsWithDocs = 0; | |
| 65 int totalDocSpan = 0; | |
| 66 | |
| 67 void visit(Element element) { | |
| 68 String comment = element.documentationComment; | |
| 69 if (comment != null) { | |
| 70 ++elementsWithDocs; | |
| 71 totalDocSpan += comment.length; | |
| 72 } | |
| 73 | |
| 74 Type type = element.runtimeType; | |
| 75 if (counts[type] == null) { | |
| 76 counts[type] = 1; | |
| 77 } else { | |
| 78 counts[type]++; | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 @override | |
| 83 visitClassElement(ClassElement element) { | |
| 84 visit(element); | |
| 85 super.visitClassElement(element); | |
| 86 } | |
| 87 | |
| 88 @override | |
| 89 visitCompilationUnitElement(CompilationUnitElement element) { | |
| 90 visit(element); | |
| 91 super.visitCompilationUnitElement(element); | |
| 92 } | |
| 93 | |
| 94 @override | |
| 95 visitConstructorElement(ConstructorElement element) { | |
| 96 visit(element); | |
| 97 super.visitConstructorElement(element); | |
| 98 } | |
| 99 | |
| 100 @override | |
| 101 visitExportElement(ExportElement element) { | |
| 102 visit(element); | |
| 103 super.visitExportElement(element); | |
| 104 } | |
| 105 | |
| 106 @override | |
| 107 visitFieldElement(FieldElement element) { | |
| 108 visit(element); | |
| 109 super.visitFieldElement(element); | |
| 110 } | |
| 111 | |
| 112 @override | |
| 113 visitFieldFormalParameterElement(FieldFormalParameterElement element) { | |
| 114 visit(element); | |
| 115 super.visitFieldFormalParameterElement(element); | |
| 116 } | |
| 117 | |
| 118 @override | |
| 119 visitFunctionElement(FunctionElement element) { | |
| 120 visit(element); | |
| 121 super.visitFunctionElement(element); | |
| 122 } | |
| 123 | |
| 124 @override | |
| 125 visitFunctionTypeAliasElement(FunctionTypeAliasElement element) { | |
| 126 visit(element); | |
| 127 super.visitFunctionTypeAliasElement(element); | |
| 128 } | |
| 129 | |
| 130 @override | |
| 131 visitImportElement(ImportElement element) { | |
| 132 visit(element); | |
| 133 super.visitImportElement(element); | |
| 134 } | |
| 135 | |
| 136 @override | |
| 137 visitLabelElement(LabelElement element) { | |
| 138 visit(element); | |
| 139 super.visitLabelElement(element); | |
| 140 } | |
| 141 | |
| 142 @override | |
| 143 visitLibraryElement(LibraryElement element) { | |
| 144 visit(element); | |
| 145 super.visitLibraryElement(element); | |
| 146 } | |
| 147 | |
| 148 @override | |
| 149 visitLocalVariableElement(LocalVariableElement element) { | |
| 150 visit(element); | |
| 151 super.visitLocalVariableElement(element); | |
| 152 } | |
| 153 | |
| 154 @override | |
| 155 visitMethodElement(MethodElement element) { | |
| 156 visit(element); | |
| 157 super.visitMethodElement(element); | |
| 158 } | |
| 159 | |
| 160 @override | |
| 161 visitMultiplyDefinedElement(MultiplyDefinedElement element) { | |
| 162 visit(element); | |
| 163 super.visitMultiplyDefinedElement(element); | |
| 164 } | |
| 165 | |
| 166 @override | |
| 167 visitParameterElement(ParameterElement element) { | |
| 168 visit(element); | |
| 169 super.visitParameterElement(element); | |
| 170 } | |
| 171 | |
| 172 @override | |
| 173 visitPrefixElement(PrefixElement element) { | |
| 174 visit(element); | |
| 175 super.visitPrefixElement(element); | |
| 176 } | |
| 177 | |
| 178 @override | |
| 179 visitPropertyAccessorElement(PropertyAccessorElement element) { | |
| 180 visit(element); | |
| 181 super.visitPropertyAccessorElement(element); | |
| 182 } | |
| 183 | |
| 184 @override | |
| 185 visitTopLevelVariableElement(TopLevelVariableElement element) { | |
| 186 visit(element); | |
| 187 super.visitTopLevelVariableElement(element); | |
| 188 } | |
| 189 | |
| 190 @override | |
| 191 visitTypeParameterElement(TypeParameterElement element) { | |
| 192 visit(element); | |
| 193 super.visitTypeParameterElement(element); | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 /** | |
| 198 * Instances of the class [GetHandler] handle GET requests. | |
| 199 */ | |
| 200 class GetHandler implements AbstractGetHandler { | |
| 201 /** | |
| 202 * The path used to request overall performance information. | |
| 203 */ | |
| 204 static const String ANALYSIS_PERFORMANCE_PATH = '/perf/analysis'; | |
| 205 | |
| 206 /** | |
| 207 * The path used to request information about a element model. | |
| 208 */ | |
| 209 static const String AST_PATH = '/ast'; | |
| 210 | |
| 211 /** | |
| 212 * The path used to request information about the cache entry corresponding | |
| 213 * to a single file. | |
| 214 */ | |
| 215 static const String CACHE_ENTRY_PATH = '/cache_entry'; | |
| 216 | |
| 217 /** | |
| 218 * The path used to request the list of source files in a certain cache | |
| 219 * state. | |
| 220 */ | |
| 221 static const String CACHE_STATE_PATH = '/cache_state'; | |
| 222 | |
| 223 /** | |
| 224 * The path used to request code completion information. | |
| 225 */ | |
| 226 static const String COMPLETION_PATH = '/completion'; | |
| 227 | |
| 228 /** | |
| 229 * The path used to request communication performance information. | |
| 230 */ | |
| 231 static const String COMMUNICATION_PERFORMANCE_PATH = '/perf/communication'; | |
| 232 | |
| 233 /** | |
| 234 * The path used to request diagnostic information for a single context. | |
| 235 */ | |
| 236 static const String CONTEXT_DIAGNOSTICS_PATH = '/diagnostic/context'; | |
| 237 | |
| 238 /** | |
| 239 * The path used to request running a validation report for a single context. | |
| 240 */ | |
| 241 static const String CONTEXT_VALIDATION_DIAGNOSTICS_PATH = | |
| 242 '/diagnostic/contextValidation'; | |
| 243 | |
| 244 /** | |
| 245 * The path used to request information about a specific context. | |
| 246 */ | |
| 247 static const String CONTEXT_PATH = '/context'; | |
| 248 | |
| 249 /** | |
| 250 * The path used to request diagnostic information. | |
| 251 */ | |
| 252 static const String DIAGNOSTIC_PATH = '/diagnostic'; | |
| 253 | |
| 254 /** | |
| 255 * The path used to request information about a element model. | |
| 256 */ | |
| 257 static const String ELEMENT_PATH = '/element'; | |
| 258 | |
| 259 /** | |
| 260 * The path used to request an analysis of the memory use of the analyzer. | |
| 261 */ | |
| 262 static const String MEMORY_USE_PATH = '/memoryUse'; | |
| 263 | |
| 264 /** | |
| 265 * The path used to request an overlay contents. | |
| 266 */ | |
| 267 static const String OVERLAY_PATH = '/overlay'; | |
| 268 | |
| 269 /** | |
| 270 * The path used to request overlays information. | |
| 271 */ | |
| 272 static const String OVERLAYS_PATH = '/overlays'; | |
| 273 | |
| 274 /** | |
| 275 * The path used to request the status of the analysis server as a whole. | |
| 276 */ | |
| 277 static const String STATUS_PATH = '/status'; | |
| 278 | |
| 279 /** | |
| 280 * Query parameter used to represent the context to search for, when | |
| 281 * accessing [CACHE_ENTRY_PATH] or [CACHE_STATE_PATH]. | |
| 282 */ | |
| 283 static const String CONTEXT_QUERY_PARAM = 'context'; | |
| 284 | |
| 285 /** | |
| 286 * Query parameter used to represent the descriptor to search for, when | |
| 287 * accessing [CACHE_STATE_PATH]. | |
| 288 */ | |
| 289 static const String DESCRIPTOR_QUERY_PARAM = 'descriptor'; | |
| 290 | |
| 291 /** | |
| 292 * Query parameter used to represent the name of elements to search for, when | |
| 293 * accessing [INDEX_ELEMENT_BY_NAME]. | |
| 294 */ | |
| 295 static const String INDEX_ELEMENT_NAME = 'name'; | |
| 296 | |
| 297 /** | |
| 298 * Query parameter used to represent the path of an overlayed file. | |
| 299 */ | |
| 300 static const String PATH_PARAM = 'path'; | |
| 301 | |
| 302 /** | |
| 303 * Query parameter used to represent the source to search for, when accessing | |
| 304 * [CACHE_ENTRY_PATH]. | |
| 305 */ | |
| 306 static const String SOURCE_QUERY_PARAM = 'entry'; | |
| 307 | |
| 308 /** | |
| 309 * Query parameter used to represent the cache state to search for, when | |
| 310 * accessing [CACHE_STATE_PATH]. | |
| 311 */ | |
| 312 static const String STATE_QUERY_PARAM = 'state'; | |
| 313 | |
| 314 static final ContentType _htmlContent = | |
| 315 new ContentType("text", "html", charset: "utf-8"); | |
| 316 | |
| 317 /** | |
| 318 * Rolling average of calls to get diagnostics. | |
| 319 */ | |
| 320 Average _diagnosticCallAverage = new Average(); | |
| 321 | |
| 322 /** | |
| 323 * The socket server whose status is to be reported on. | |
| 324 */ | |
| 325 SocketServer _server; | |
| 326 | |
| 327 /** | |
| 328 * Buffer containing strings printed by the analysis server. | |
| 329 */ | |
| 330 List<String> _printBuffer; | |
| 331 | |
| 332 /** | |
| 333 * Contents of overlay files. | |
| 334 */ | |
| 335 final Map<String, String> _overlayContents = <String, String>{}; | |
| 336 | |
| 337 /** | |
| 338 * Handler for diagnostics requests. | |
| 339 */ | |
| 340 DiagnosticDomainHandler _diagnosticHandler; | |
| 341 | |
| 342 /** | |
| 343 * Initialize a newly created handler for GET requests. | |
| 344 */ | |
| 345 GetHandler(this._server, this._printBuffer); | |
| 346 | |
| 347 DiagnosticDomainHandler get diagnosticHandler { | |
| 348 if (_diagnosticHandler == null) { | |
| 349 _diagnosticHandler = new DiagnosticDomainHandler(_server.analysisServer); | |
| 350 } | |
| 351 return _diagnosticHandler; | |
| 352 } | |
| 353 | |
| 354 /** | |
| 355 * Return the active [CompletionDomainHandler] | |
| 356 * or `null` if either analysis server is not running | |
| 357 * or there is no completion domain handler. | |
| 358 */ | |
| 359 CompletionDomainHandler get _completionDomainHandler { | |
| 360 AnalysisServer analysisServer = _server.analysisServer; | |
| 361 if (analysisServer == null) { | |
| 362 return null; | |
| 363 } | |
| 364 return analysisServer.handlers | |
| 365 .firstWhere((h) => h is CompletionDomainHandler, orElse: () => null); | |
| 366 } | |
| 367 | |
| 368 /** | |
| 369 * Handle a GET request received by the HTTP server. | |
| 370 */ | |
| 371 void handleGetRequest(HttpRequest request) { | |
| 372 String path = request.uri.path; | |
| 373 if (path == '/' || path == STATUS_PATH) { | |
| 374 _returnServerStatus(request); | |
| 375 } else if (path == ANALYSIS_PERFORMANCE_PATH) { | |
| 376 _returnAnalysisPerformance(request); | |
| 377 } else if (path == AST_PATH) { | |
| 378 _returnAst(request); | |
| 379 } else if (path == CACHE_STATE_PATH) { | |
| 380 _returnCacheState(request); | |
| 381 } else if (path == CACHE_ENTRY_PATH) { | |
| 382 _returnCacheEntry(request); | |
| 383 } else if (path == COMPLETION_PATH) { | |
| 384 _returnCompletionInfo(request); | |
| 385 } else if (path == COMMUNICATION_PERFORMANCE_PATH) { | |
| 386 _returnCommunicationPerformance(request); | |
| 387 } else if (path == CONTEXT_DIAGNOSTICS_PATH) { | |
| 388 _returnContextDiagnostics(request); | |
| 389 } else if (path == CONTEXT_VALIDATION_DIAGNOSTICS_PATH) { | |
| 390 _returnContextValidationDiagnostics(request); | |
| 391 } else if (path == CONTEXT_PATH) { | |
| 392 _returnContextInfo(request); | |
| 393 } else if (path == DIAGNOSTIC_PATH) { | |
| 394 _returnDiagnosticInfo(request); | |
| 395 } else if (path == ELEMENT_PATH) { | |
| 396 _returnElement(request); | |
| 397 } else if (path == MEMORY_USE_PATH) { | |
| 398 _returnMemoryUsage(request); | |
| 399 } else if (path == OVERLAY_PATH) { | |
| 400 _returnOverlayContents(request); | |
| 401 } else if (path == OVERLAYS_PATH) { | |
| 402 _returnOverlaysInfo(request); | |
| 403 } else { | |
| 404 _returnUnknownRequest(request); | |
| 405 } | |
| 406 } | |
| 407 | |
| 408 /** | |
| 409 * Produce an encoded version of the given [descriptor] that can be used to | |
| 410 * find the descriptor later. | |
| 411 */ | |
| 412 String _encodeSdkDescriptor(SdkDescription descriptor) { | |
| 413 StringBuffer buffer = new StringBuffer(); | |
| 414 buffer.write(descriptor.options.signature.join(',')); | |
| 415 for (String path in descriptor.paths) { | |
| 416 buffer.write('+'); | |
| 417 buffer.write(path); | |
| 418 } | |
| 419 return buffer.toString(); | |
| 420 } | |
| 421 | |
| 422 /** | |
| 423 * Return the folder being managed by the given [analysisServer] that matches | |
| 424 * the given [contextFilter], or `null` if there is none. | |
| 425 */ | |
| 426 Folder _findFolder(AnalysisServer analysisServer, String contextFilter) { | |
| 427 return analysisServer.folderMap.keys.firstWhere( | |
| 428 (Folder folder) => folder.path == contextFilter, | |
| 429 orElse: () => null); | |
| 430 } | |
| 431 | |
| 432 /** | |
| 433 * Return any AST structure stored in the given [entry]. | |
| 434 */ | |
| 435 CompilationUnit _getAnyAst(CacheEntry entry) { | |
| 436 CompilationUnit unit = entry.getValue(PARSED_UNIT); | |
| 437 if (unit != null) { | |
| 438 return unit; | |
| 439 } | |
| 440 unit = entry.getValue(RESOLVED_UNIT1); | |
| 441 if (unit != null) { | |
| 442 return unit; | |
| 443 } | |
| 444 unit = entry.getValue(RESOLVED_UNIT2); | |
| 445 if (unit != null) { | |
| 446 return unit; | |
| 447 } | |
| 448 unit = entry.getValue(RESOLVED_UNIT3); | |
| 449 if (unit != null) { | |
| 450 return unit; | |
| 451 } | |
| 452 unit = entry.getValue(RESOLVED_UNIT4); | |
| 453 if (unit != null) { | |
| 454 return unit; | |
| 455 } | |
| 456 unit = entry.getValue(RESOLVED_UNIT5); | |
| 457 if (unit != null) { | |
| 458 return unit; | |
| 459 } | |
| 460 unit = entry.getValue(RESOLVED_UNIT6); | |
| 461 if (unit != null) { | |
| 462 return unit; | |
| 463 } | |
| 464 unit = entry.getValue(RESOLVED_UNIT7); | |
| 465 if (unit != null) { | |
| 466 return unit; | |
| 467 } | |
| 468 unit = entry.getValue(RESOLVED_UNIT8); | |
| 469 if (unit != null) { | |
| 470 return unit; | |
| 471 } | |
| 472 unit = entry.getValue(RESOLVED_UNIT9); | |
| 473 if (unit != null) { | |
| 474 return unit; | |
| 475 } | |
| 476 unit = entry.getValue(RESOLVED_UNIT10); | |
| 477 if (unit != null) { | |
| 478 return unit; | |
| 479 } | |
| 480 unit = entry.getValue(RESOLVED_UNIT11); | |
| 481 if (unit != null) { | |
| 482 return unit; | |
| 483 } | |
| 484 unit = entry.getValue(RESOLVED_UNIT12); | |
| 485 if (unit != null) { | |
| 486 return unit; | |
| 487 } | |
| 488 return entry.getValue(RESOLVED_UNIT); | |
| 489 } | |
| 490 | |
| 491 /** | |
| 492 * Return a list of the result descriptors whose state should be displayed for | |
| 493 * the given cache [entry]. | |
| 494 */ | |
| 495 List<ResultDescriptor> _getExpectedResults(CacheEntry entry) { | |
| 496 AnalysisTarget target = entry.target; | |
| 497 Set<ResultDescriptor> results = entry.nonInvalidResults.toSet(); | |
| 498 if (target is Source) { | |
| 499 String name = target.shortName; | |
| 500 results.add(CONTENT); | |
| 501 results.add(LINE_INFO); | |
| 502 results.add(MODIFICATION_TIME); | |
| 503 if (AnalysisEngine.isDartFileName(name)) { | |
| 504 results.add(BUILD_DIRECTIVES_ERRORS); | |
| 505 results.add(BUILD_LIBRARY_ERRORS); | |
| 506 results.add(CONTAINING_LIBRARIES); | |
| 507 results.add(DART_ERRORS); | |
| 508 results.add(EXPLICITLY_IMPORTED_LIBRARIES); | |
| 509 results.add(EXPORT_SOURCE_CLOSURE); | |
| 510 results.add(EXPORTED_LIBRARIES); | |
| 511 results.add(IMPORTED_LIBRARIES); | |
| 512 results.add(INCLUDED_PARTS); | |
| 513 results.add(IS_LAUNCHABLE); | |
| 514 results.add(LIBRARY_ELEMENT1); | |
| 515 results.add(LIBRARY_ELEMENT2); | |
| 516 results.add(LIBRARY_ELEMENT3); | |
| 517 results.add(LIBRARY_ELEMENT4); | |
| 518 results.add(LIBRARY_ELEMENT5); | |
| 519 results.add(LIBRARY_ELEMENT6); | |
| 520 results.add(LIBRARY_ELEMENT); | |
| 521 results.add(LIBRARY_ERRORS_READY); | |
| 522 results.add(PARSE_ERRORS); | |
| 523 results.add(PARSED_UNIT); | |
| 524 results.add(SCAN_ERRORS); | |
| 525 results.add(SOURCE_KIND); | |
| 526 results.add(TOKEN_STREAM); | |
| 527 results.add(UNITS); | |
| 528 } else if (AnalysisEngine.isHtmlFileName(name)) { | |
| 529 results.add(DART_SCRIPTS); | |
| 530 results.add(HTML_DOCUMENT); | |
| 531 results.add(HTML_DOCUMENT_ERRORS); | |
| 532 results.add(HTML_ERRORS); | |
| 533 results.add(REFERENCED_LIBRARIES); | |
| 534 } else if (AnalysisEngine.isAnalysisOptionsFileName(name)) { | |
| 535 results.add(ANALYSIS_OPTIONS_ERRORS); | |
| 536 } | |
| 537 } else if (target is LibrarySpecificUnit) { | |
| 538 results.add(COMPILATION_UNIT_CONSTANTS); | |
| 539 results.add(COMPILATION_UNIT_ELEMENT); | |
| 540 results.add(HINTS); | |
| 541 results.add(LINTS); | |
| 542 results.add(INFERABLE_STATIC_VARIABLES_IN_UNIT); | |
| 543 results.add(LIBRARY_UNIT_ERRORS); | |
| 544 results.add(RESOLVE_DIRECTIVES_ERRORS); | |
| 545 results.add(RESOLVE_TYPE_NAMES_ERRORS); | |
| 546 results.add(RESOLVE_TYPE_BOUNDS_ERRORS); | |
| 547 results.add(RESOLVE_UNIT_ERRORS); | |
| 548 results.add(RESOLVED_UNIT1); | |
| 549 results.add(RESOLVED_UNIT2); | |
| 550 results.add(RESOLVED_UNIT3); | |
| 551 results.add(RESOLVED_UNIT4); | |
| 552 results.add(RESOLVED_UNIT5); | |
| 553 results.add(RESOLVED_UNIT6); | |
| 554 results.add(RESOLVED_UNIT7); | |
| 555 results.add(RESOLVED_UNIT8); | |
| 556 results.add(RESOLVED_UNIT9); | |
| 557 results.add(RESOLVED_UNIT10); | |
| 558 results.add(RESOLVED_UNIT11); | |
| 559 results.add(RESOLVED_UNIT12); | |
| 560 results.add(RESOLVED_UNIT); | |
| 561 results.add(STRONG_MODE_ERRORS); | |
| 562 results.add(USED_IMPORTED_ELEMENTS); | |
| 563 results.add(USED_LOCAL_ELEMENTS); | |
| 564 results.add(VARIABLE_REFERENCE_ERRORS); | |
| 565 results.add(VERIFY_ERRORS); | |
| 566 } else if (target is ConstantEvaluationTarget) { | |
| 567 results.add(CONSTANT_DEPENDENCIES); | |
| 568 results.add(CONSTANT_VALUE); | |
| 569 if (target is VariableElement) { | |
| 570 results.add(INFERABLE_STATIC_VARIABLE_DEPENDENCIES); | |
| 571 results.add(INFERRED_STATIC_VARIABLE); | |
| 572 } | |
| 573 } else if (target is AnalysisContextTarget) { | |
| 574 results.add(TYPE_PROVIDER); | |
| 575 } | |
| 576 return results.toList(); | |
| 577 } | |
| 578 | |
| 579 /** | |
| 580 * Return the context for the SDK whose descriptor is encoded to be the same | |
| 581 * as the given [contextFilter]. The [analysisServer] is used to access the | |
| 582 * SDKs. | |
| 583 */ | |
| 584 AnalysisContext _getSdkContext( | |
| 585 AnalysisServer analysisServer, String contextFilter) { | |
| 586 DartSdkManager manager = analysisServer.sdkManager; | |
| 587 List<SdkDescription> descriptors = manager.sdkDescriptors; | |
| 588 for (SdkDescription descriptor in descriptors) { | |
| 589 if (contextFilter == _encodeSdkDescriptor(descriptor)) { | |
| 590 return manager.getSdk(descriptor, () => null)?.context; | |
| 591 } | |
| 592 } | |
| 593 return null; | |
| 594 } | |
| 595 | |
| 596 /** | |
| 597 * Return `true` if the given analysis [context] has at least one entry with | |
| 598 * an exception. | |
| 599 */ | |
| 600 bool _hasException(InternalAnalysisContext context) { | |
| 601 if (context == null) { | |
| 602 return false; | |
| 603 } | |
| 604 MapIterator<AnalysisTarget, CacheEntry> iterator = | |
| 605 context.analysisCache.iterator(); | |
| 606 while (iterator.moveNext()) { | |
| 607 CacheEntry entry = iterator.value; | |
| 608 if (entry == null || entry.exception != null) { | |
| 609 return true; | |
| 610 } | |
| 611 } | |
| 612 return false; | |
| 613 } | |
| 614 | |
| 615 /** | |
| 616 * Return the folder in the [folderMap] with which the given [context] is | |
| 617 * associated. | |
| 618 */ | |
| 619 Folder _keyForValue( | |
| 620 Map<Folder, AnalysisContext> folderMap, AnalysisContext context) { | |
| 621 for (Folder folder in folderMap.keys) { | |
| 622 if (folderMap[folder] == context) { | |
| 623 return folder; | |
| 624 } | |
| 625 } | |
| 626 return null; | |
| 627 } | |
| 628 | |
| 629 /** | |
| 630 * Return a response displaying overall performance information. | |
| 631 */ | |
| 632 void _returnAnalysisPerformance(HttpRequest request) { | |
| 633 AnalysisServer analysisServer = _server.analysisServer; | |
| 634 if (analysisServer == null) { | |
| 635 return _returnFailure(request, 'Analysis server is not running'); | |
| 636 } | |
| 637 _writeResponse(request, (StringBuffer buffer) { | |
| 638 _writePage(buffer, 'Analysis Server - Analysis Performance', [], | |
| 639 (StringBuffer buffer) { | |
| 640 buffer.write('<h3>Analysis Performance</h3>'); | |
| 641 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 642 // | |
| 643 // Write performance tags. | |
| 644 // | |
| 645 buffer.write('<p><b>Performance tag data</b></p>'); | |
| 646 buffer.write( | |
| 647 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 648 _writeRow(buffer, ['Time (in ms)', 'Percent', 'Tag name'], | |
| 649 header: true); | |
| 650 // prepare sorted tags | |
| 651 List<PerformanceTag> tags = PerformanceTag.all.toList(); | |
| 652 tags.remove(ServerPerformanceStatistics.idle); | |
| 653 tags.sort((a, b) => b.elapsedMs - a.elapsedMs); | |
| 654 // prepare total time | |
| 655 int totalTagTime = 0; | |
| 656 tags.forEach((PerformanceTag tag) { | |
| 657 totalTagTime += tag.elapsedMs; | |
| 658 }); | |
| 659 // write rows | |
| 660 void writeRow(PerformanceTag tag) { | |
| 661 double percent = (tag.elapsedMs * 100) / totalTagTime; | |
| 662 String percentStr = '${percent.toStringAsFixed(2)}%'; | |
| 663 _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label], | |
| 664 classes: ["right", "right", null]); | |
| 665 } | |
| 666 | |
| 667 tags.forEach(writeRow); | |
| 668 buffer.write('</table>'); | |
| 669 // | |
| 670 // Write target counts. | |
| 671 // | |
| 672 void incrementCount(Map<String, int> counts, String key) { | |
| 673 int count = counts[key]; | |
| 674 if (count == null) { | |
| 675 count = 1; | |
| 676 } else { | |
| 677 count++; | |
| 678 } | |
| 679 counts[key] = count; | |
| 680 } | |
| 681 | |
| 682 Set<AnalysisTarget> countedTargets = new HashSet<AnalysisTarget>(); | |
| 683 Map<String, int> sourceTypeCounts = new HashMap<String, int>(); | |
| 684 Map<String, int> typeCounts = new HashMap<String, int>(); | |
| 685 int explicitSourceCount = 0; | |
| 686 int explicitLineInfoCount = 0; | |
| 687 int explicitLineCount = 0; | |
| 688 int implicitSourceCount = 0; | |
| 689 int implicitLineInfoCount = 0; | |
| 690 int implicitLineCount = 0; | |
| 691 for (InternalAnalysisContext context | |
| 692 in analysisServer.analysisContexts) { | |
| 693 Set<Source> explicitSources = new HashSet<Source>(); | |
| 694 Set<Source> implicitSources = new HashSet<Source>(); | |
| 695 AnalysisCache cache = context.analysisCache; | |
| 696 MapIterator<AnalysisTarget, CacheEntry> iterator = cache.iterator(); | |
| 697 while (iterator.moveNext()) { | |
| 698 AnalysisTarget target = iterator.key; | |
| 699 if (countedTargets.add(target)) { | |
| 700 if (target is Source) { | |
| 701 String name = target.fullName; | |
| 702 String sourceName; | |
| 703 if (AnalysisEngine.isDartFileName(name)) { | |
| 704 if (iterator.value.explicitlyAdded) { | |
| 705 explicitSources.add(target); | |
| 706 sourceName = 'Dart file (explicit)'; | |
| 707 } else { | |
| 708 implicitSources.add(target); | |
| 709 sourceName = 'Dart file (implicit)'; | |
| 710 } | |
| 711 } else if (AnalysisEngine.isHtmlFileName(name)) { | |
| 712 if (iterator.value.explicitlyAdded) { | |
| 713 sourceName = 'Html file (explicit)'; | |
| 714 } else { | |
| 715 sourceName = 'Html file (implicit)'; | |
| 716 } | |
| 717 } else { | |
| 718 if (iterator.value.explicitlyAdded) { | |
| 719 sourceName = 'Unknown file (explicit)'; | |
| 720 } else { | |
| 721 sourceName = 'Unknown file (implicit)'; | |
| 722 } | |
| 723 } | |
| 724 incrementCount(sourceTypeCounts, sourceName); | |
| 725 } else if (target is ConstantEvaluationTarget) { | |
| 726 incrementCount(typeCounts, 'ConstantEvaluationTarget'); | |
| 727 } else { | |
| 728 String typeName = target.runtimeType.toString(); | |
| 729 incrementCount(typeCounts, typeName); | |
| 730 } | |
| 731 } | |
| 732 } | |
| 733 | |
| 734 int lineCount(Set<Source> sources, bool explicit) { | |
| 735 return sources.fold(0, (int previousTotal, Source source) { | |
| 736 LineInfo lineInfo = context.getLineInfo(source); | |
| 737 if (lineInfo is LineInfoWithCount) { | |
| 738 if (explicit) { | |
| 739 explicitLineInfoCount++; | |
| 740 } else { | |
| 741 implicitLineInfoCount++; | |
| 742 } | |
| 743 return previousTotal + lineInfo.lineCount; | |
| 744 } else { | |
| 745 return previousTotal; | |
| 746 } | |
| 747 }); | |
| 748 } | |
| 749 | |
| 750 explicitSourceCount += explicitSources.length; | |
| 751 explicitLineCount += lineCount(explicitSources, true); | |
| 752 implicitSourceCount += implicitSources.length; | |
| 753 implicitLineCount += lineCount(implicitSources, false); | |
| 754 } | |
| 755 List<String> sourceTypeNames = sourceTypeCounts.keys.toList(); | |
| 756 sourceTypeNames.sort(); | |
| 757 List<String> typeNames = typeCounts.keys.toList(); | |
| 758 typeNames.sort(); | |
| 759 | |
| 760 buffer.write('<p><b>Target counts</b></p>'); | |
| 761 buffer.write( | |
| 762 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 763 _writeRow(buffer, ['Target', 'Count'], header: true); | |
| 764 for (String sourceTypeName in sourceTypeNames) { | |
| 765 _writeRow( | |
| 766 buffer, [sourceTypeName, sourceTypeCounts[sourceTypeName]], | |
| 767 classes: [null, "right"]); | |
| 768 } | |
| 769 for (String typeName in typeNames) { | |
| 770 _writeRow(buffer, [typeName, typeCounts[typeName]], | |
| 771 classes: [null, "right"]); | |
| 772 } | |
| 773 buffer.write('</table>'); | |
| 774 | |
| 775 buffer.write('<p><b>Line counts</b></p>'); | |
| 776 buffer.write( | |
| 777 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 778 _writeRow(buffer, ['Kind', 'Lines of Code', 'Source Counts'], | |
| 779 header: true); | |
| 780 _writeRow(buffer, [ | |
| 781 'Explicit', | |
| 782 explicitLineCount.toString(), | |
| 783 '$explicitLineInfoCount / $explicitSourceCount' | |
| 784 ], classes: [ | |
| 785 null, | |
| 786 "right" | |
| 787 ]); | |
| 788 _writeRow(buffer, [ | |
| 789 'Implicit', | |
| 790 implicitLineCount.toString(), | |
| 791 '$implicitLineInfoCount / $implicitSourceCount' | |
| 792 ], classes: [ | |
| 793 null, | |
| 794 "right" | |
| 795 ]); | |
| 796 _writeRow(buffer, [ | |
| 797 'Total', | |
| 798 (explicitLineCount + implicitLineCount).toString(), | |
| 799 '${explicitLineInfoCount + implicitLineInfoCount} / ${explicitSource
Count + implicitSourceCount}' | |
| 800 ], classes: [ | |
| 801 null, | |
| 802 "right" | |
| 803 ]); | |
| 804 buffer.write('</table>'); | |
| 805 | |
| 806 Map<ResultDescriptor, int> recomputedCounts = | |
| 807 CacheEntry.recomputedCounts; | |
| 808 List<ResultDescriptor> descriptors = recomputedCounts.keys.toList(); | |
| 809 descriptors.sort(ResultDescriptor.SORT_BY_NAME); | |
| 810 buffer.write('<p><b>Results computed after being flushed</b></p>'); | |
| 811 buffer.write( | |
| 812 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 813 _writeRow(buffer, ['Result', 'Count'], header: true); | |
| 814 for (ResultDescriptor descriptor in descriptors) { | |
| 815 _writeRow(buffer, [descriptor.name, recomputedCounts[descriptor]], | |
| 816 classes: [null, "right"]); | |
| 817 } | |
| 818 buffer.write('</table>'); | |
| 819 | |
| 820 { | |
| 821 buffer.write('<p><b>Cache consistency statistics</b></p>'); | |
| 822 buffer.write( | |
| 823 '<table style="border-collapse: separate; border-spacing: 10px 5
px;">'); | |
| 824 _writeRow(buffer, ['Name', 'Count'], header: true); | |
| 825 _writeRow(buffer, [ | |
| 826 'Changed', | |
| 827 PerformanceStatistics | |
| 828 .cacheConsistencyValidationStatistics.numOfChanged | |
| 829 ], classes: [ | |
| 830 null, | |
| 831 "right" | |
| 832 ]); | |
| 833 _writeRow(buffer, [ | |
| 834 'Removed', | |
| 835 PerformanceStatistics | |
| 836 .cacheConsistencyValidationStatistics.numOfRemoved | |
| 837 ], classes: [ | |
| 838 null, | |
| 839 "right" | |
| 840 ]); | |
| 841 buffer.write('</table>'); | |
| 842 } | |
| 843 }, (StringBuffer buffer) { | |
| 844 // | |
| 845 // Write task model timing information. | |
| 846 // | |
| 847 buffer.write('<p><b>Task performance data</b></p>'); | |
| 848 buffer.write( | |
| 849 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 850 _writeRow( | |
| 851 buffer, | |
| 852 [ | |
| 853 'Task Name', | |
| 854 'Count', | |
| 855 'Total Time (in ms)', | |
| 856 'Average Time (in ms)' | |
| 857 ], | |
| 858 header: true); | |
| 859 | |
| 860 Map<Type, int> countMap = AnalysisTask.countMap; | |
| 861 Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap; | |
| 862 List<Type> taskClasses = stopwatchMap.keys.toList(); | |
| 863 taskClasses.sort((Type first, Type second) => | |
| 864 first.toString().compareTo(second.toString())); | |
| 865 int totalTaskTime = 0; | |
| 866 taskClasses.forEach((Type taskClass) { | |
| 867 int count = countMap[taskClass]; | |
| 868 if (count == null) { | |
| 869 count = 0; | |
| 870 } | |
| 871 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds; | |
| 872 totalTaskTime += taskTime; | |
| 873 _writeRow(buffer, [ | |
| 874 taskClass.toString(), | |
| 875 count, | |
| 876 taskTime, | |
| 877 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3) | |
| 878 ], classes: [ | |
| 879 null, | |
| 880 "right", | |
| 881 "right", | |
| 882 "right" | |
| 883 ]); | |
| 884 }); | |
| 885 _writeRow(buffer, ['Total', '-', totalTaskTime, '-'], | |
| 886 classes: [null, "right", "right", "right"]); | |
| 887 buffer.write('</table>'); | |
| 888 }); | |
| 889 }); | |
| 890 }); | |
| 891 } | |
| 892 | |
| 893 /** | |
| 894 * Return a response containing information about an AST structure. | |
| 895 */ | |
| 896 void _returnAst(HttpRequest request) { | |
| 897 AnalysisServer analysisServer = _server.analysisServer; | |
| 898 if (analysisServer == null) { | |
| 899 return _returnFailure(request, 'Analysis server not running'); | |
| 900 } | |
| 901 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 902 if (contextFilter == null) { | |
| 903 return _returnFailure( | |
| 904 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 905 } | |
| 906 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 907 if (folder == null) { | |
| 908 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 909 } | |
| 910 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; | |
| 911 if (sourceUri == null) { | |
| 912 return _returnFailure( | |
| 913 request, 'Query parameter $SOURCE_QUERY_PARAM required'); | |
| 914 } | |
| 915 | |
| 916 InternalAnalysisContext context = analysisServer.folderMap[folder]; | |
| 917 | |
| 918 _writeResponse(request, (StringBuffer buffer) { | |
| 919 _writePage(buffer, 'Analysis Server - AST Structure', | |
| 920 ['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) { | |
| 921 Source source = context.sourceFactory.forUri(sourceUri); | |
| 922 if (source == null) { | |
| 923 buffer.write('<p>Not found.</p>'); | |
| 924 return; | |
| 925 } | |
| 926 List<Source> libraries = context.getLibrariesContaining(source); | |
| 927 for (Source library in libraries) { | |
| 928 AnalysisTarget target = new LibrarySpecificUnit(library, source); | |
| 929 CacheEntry entry = context.analysisCache.get(target); | |
| 930 buffer.write('<b>$target</b><br>'); | |
| 931 if (entry == null) { | |
| 932 buffer.write('<p>Not found.</p>'); | |
| 933 continue; | |
| 934 } | |
| 935 CompilationUnit ast = _getAnyAst(entry); | |
| 936 if (ast == null) { | |
| 937 buffer.write('<p>null</p>'); | |
| 938 continue; | |
| 939 } | |
| 940 AstWriter writer = new AstWriter(buffer); | |
| 941 ast.accept(writer); | |
| 942 if (writer.exceptions.isNotEmpty) { | |
| 943 buffer.write('<h3>Exceptions while creating page</h3>'); | |
| 944 for (CaughtException exception in writer.exceptions) { | |
| 945 _writeException(buffer, exception); | |
| 946 } | |
| 947 } | |
| 948 } | |
| 949 }); | |
| 950 }); | |
| 951 } | |
| 952 | |
| 953 /** | |
| 954 * Return a response containing information about a single source file in the | |
| 955 * cache. | |
| 956 */ | |
| 957 void _returnCacheEntry(HttpRequest request) { | |
| 958 AnalysisServer analysisServer = _server.analysisServer; | |
| 959 if (analysisServer == null) { | |
| 960 return _returnFailure(request, 'Analysis server not running'); | |
| 961 } | |
| 962 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 963 if (contextFilter == null) { | |
| 964 return _returnFailure( | |
| 965 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 966 } | |
| 967 InternalAnalysisContext context = null; | |
| 968 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 969 if (folder == null) { | |
| 970 context = _getSdkContext(analysisServer, contextFilter); | |
| 971 if (context == null) { | |
| 972 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 973 } | |
| 974 return _returnFailure(request, | |
| 975 'Cannot view cache entries from an SDK context: $contextFilter'); | |
| 976 } else { | |
| 977 context = analysisServer.folderMap[folder]; | |
| 978 } | |
| 979 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; | |
| 980 if (sourceUri == null) { | |
| 981 return _returnFailure( | |
| 982 request, 'Query parameter $SOURCE_QUERY_PARAM required'); | |
| 983 } | |
| 984 | |
| 985 List<Folder> allContexts = <Folder>[]; | |
| 986 Map<Folder, List<CacheEntry>> entryMap = | |
| 987 new HashMap<Folder, List<CacheEntry>>(); | |
| 988 StringBuffer invalidKeysBuffer = new StringBuffer(); | |
| 989 analysisServer.folderMap.forEach((Folder folder, AnalysisContext context) { | |
| 990 Source source = context.sourceFactory.forUri(sourceUri); | |
| 991 if (source != null) { | |
| 992 MapIterator<AnalysisTarget, CacheEntry> iterator = | |
| 993 (context as InternalAnalysisContext).analysisCache.iterator(); | |
| 994 while (iterator.moveNext()) { | |
| 995 if (source == iterator.key.source) { | |
| 996 if (!allContexts.contains(folder)) { | |
| 997 allContexts.add(folder); | |
| 998 } | |
| 999 List<CacheEntry> entries = entryMap[folder]; | |
| 1000 if (entries == null) { | |
| 1001 entries = <CacheEntry>[]; | |
| 1002 entryMap[folder] = entries; | |
| 1003 } | |
| 1004 CacheEntry value = iterator.value; | |
| 1005 if (value == null) { | |
| 1006 if (invalidKeysBuffer.isNotEmpty) { | |
| 1007 invalidKeysBuffer.write(', '); | |
| 1008 } | |
| 1009 invalidKeysBuffer.write(iterator.key.toString()); | |
| 1010 } else { | |
| 1011 entries.add(value); | |
| 1012 } | |
| 1013 } | |
| 1014 } | |
| 1015 } | |
| 1016 }); | |
| 1017 allContexts.sort((Folder firstFolder, Folder secondFolder) => | |
| 1018 firstFolder.path.compareTo(secondFolder.path)); | |
| 1019 | |
| 1020 _writeResponse(request, (StringBuffer buffer) { | |
| 1021 _writePage(buffer, 'Analysis Server - Cache Entry', | |
| 1022 ['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) { | |
| 1023 if (invalidKeysBuffer.isNotEmpty) { | |
| 1024 buffer.write('<h3>Targets with null Entries</h3><p>'); | |
| 1025 buffer.write(invalidKeysBuffer.toString()); | |
| 1026 buffer.write('</p>'); | |
| 1027 } | |
| 1028 List<CacheEntry> entries = entryMap[folder]; | |
| 1029 buffer.write('<h3>Analyzing Contexts</h3><p>'); | |
| 1030 bool first = true; | |
| 1031 allContexts.forEach((Folder folder) { | |
| 1032 if (first) { | |
| 1033 first = false; | |
| 1034 } else { | |
| 1035 buffer.write('<br>'); | |
| 1036 } | |
| 1037 InternalAnalysisContext analyzingContext = | |
| 1038 analysisServer.folderMap[folder]; | |
| 1039 if (analyzingContext == context) { | |
| 1040 buffer.write(folder.path); | |
| 1041 } else { | |
| 1042 buffer.write(makeLink( | |
| 1043 CACHE_ENTRY_PATH, | |
| 1044 { | |
| 1045 CONTEXT_QUERY_PARAM: folder.path, | |
| 1046 SOURCE_QUERY_PARAM: sourceUri | |
| 1047 }, | |
| 1048 HTML_ESCAPE.convert(folder.path))); | |
| 1049 } | |
| 1050 if (entries == null) { | |
| 1051 buffer.write(' (file does not exist)'); | |
| 1052 } else { | |
| 1053 CacheEntry sourceEntry = entries | |
| 1054 .firstWhere((CacheEntry entry) => entry.target is Source); | |
| 1055 if (sourceEntry == null) { | |
| 1056 buffer.write(' (missing source entry)'); | |
| 1057 } else if (sourceEntry.explicitlyAdded) { | |
| 1058 buffer.write(' (explicit)'); | |
| 1059 } else { | |
| 1060 buffer.write(' (implicit)'); | |
| 1061 } | |
| 1062 } | |
| 1063 }); | |
| 1064 buffer.write('</p>'); | |
| 1065 | |
| 1066 if (entries == null) { | |
| 1067 buffer.write('<p>Not being analyzed in this context.</p>'); | |
| 1068 return; | |
| 1069 } | |
| 1070 for (CacheEntry entry in entries) { | |
| 1071 Map<String, String> linkParameters = <String, String>{ | |
| 1072 CONTEXT_QUERY_PARAM: contextFilter, | |
| 1073 SOURCE_QUERY_PARAM: sourceUri | |
| 1074 }; | |
| 1075 List<ResultDescriptor> results = _getExpectedResults(entry); | |
| 1076 results.sort(ResultDescriptor.SORT_BY_NAME); | |
| 1077 | |
| 1078 buffer.write('<h3>'); | |
| 1079 buffer.write(HTML_ESCAPE.convert(entry.target.toString())); | |
| 1080 buffer.write('</h3>'); | |
| 1081 buffer.write('<p>time</p><blockquote><p>Value</p><blockquote>'); | |
| 1082 buffer.write(entry.modificationTime); | |
| 1083 buffer.write('</blockquote></blockquote>'); | |
| 1084 for (ResultDescriptor result in results) { | |
| 1085 ResultData data = entry.getResultData(result); | |
| 1086 CacheState state = entry.getState(result); | |
| 1087 String descriptorName = HTML_ESCAPE.convert(result.toString()); | |
| 1088 String descriptorState = HTML_ESCAPE.convert(state.toString()); | |
| 1089 buffer | |
| 1090 .write('<p>$descriptorName ($descriptorState)</p><blockquote>'); | |
| 1091 if (state == CacheState.VALID) { | |
| 1092 buffer.write('<p>Value</p><blockquote>'); | |
| 1093 try { | |
| 1094 _writeValueAsHtml( | |
| 1095 buffer, entry.getValue(result), linkParameters); | |
| 1096 } catch (exception) { | |
| 1097 buffer.write('(${HTML_ESCAPE.convert(exception.toString())})'); | |
| 1098 } | |
| 1099 buffer.write('</blockquote>'); | |
| 1100 } | |
| 1101 _writeTargetedResults(buffer, 'Depends on', data.dependedOnResults); | |
| 1102 _writeTargetedResults( | |
| 1103 buffer, 'Depended on by', data.dependentResults); | |
| 1104 buffer.write('</blockquote>'); | |
| 1105 } | |
| 1106 if (entry.exception != null) { | |
| 1107 buffer.write('<dt>exception</dt><dd>'); | |
| 1108 _writeException(buffer, entry.exception); | |
| 1109 buffer.write('</dd>'); | |
| 1110 } | |
| 1111 } | |
| 1112 }); | |
| 1113 }); | |
| 1114 } | |
| 1115 | |
| 1116 /** | |
| 1117 * Return a response indicating the set of source files in a certain cache | |
| 1118 * state. | |
| 1119 */ | |
| 1120 void _returnCacheState(HttpRequest request) { | |
| 1121 AnalysisServer analysisServer = _server.analysisServer; | |
| 1122 if (analysisServer == null) { | |
| 1123 return _returnFailure(request, 'Analysis server not running'); | |
| 1124 } | |
| 1125 // Figure out which context is being searched within. | |
| 1126 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 1127 if (contextFilter == null) { | |
| 1128 return _returnFailure( | |
| 1129 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 1130 } | |
| 1131 // Figure out what CacheState is being searched for. | |
| 1132 String stateQueryParam = request.uri.queryParameters[STATE_QUERY_PARAM]; | |
| 1133 if (stateQueryParam == null) { | |
| 1134 return _returnFailure( | |
| 1135 request, 'Query parameter $STATE_QUERY_PARAM required'); | |
| 1136 } | |
| 1137 CacheState stateFilter = null; | |
| 1138 for (CacheState value in CacheState.values) { | |
| 1139 if (value.toString() == stateQueryParam) { | |
| 1140 stateFilter = value; | |
| 1141 } | |
| 1142 } | |
| 1143 if (stateFilter == null) { | |
| 1144 return _returnFailure( | |
| 1145 request, 'Query parameter $STATE_QUERY_PARAM is invalid'); | |
| 1146 } | |
| 1147 // Figure out which descriptor is being searched for. | |
| 1148 String descriptorFilter = | |
| 1149 request.uri.queryParameters[DESCRIPTOR_QUERY_PARAM]; | |
| 1150 if (descriptorFilter == null) { | |
| 1151 return _returnFailure( | |
| 1152 request, 'Query parameter $DESCRIPTOR_QUERY_PARAM required'); | |
| 1153 } | |
| 1154 | |
| 1155 // TODO(brianwilkerson) Figure out how to convert the 'descriptorFilter' to | |
| 1156 // a ResultDescriptor so that we can query the state, then uncomment the | |
| 1157 // code below that computes and prints the list of links. | |
| 1158 // Folder folder = _findFolder(analysisServer, contextFilter); | |
| 1159 // InternalAnalysisContext context = analysisServer.folderMap[folder]; | |
| 1160 // List<String> links = <String>[]; | |
| 1161 // MapIterator<AnalysisTarget, CacheEntry> iterator = context.analysisCache.i
terator(); | |
| 1162 // while (iterator.moveNext()) { | |
| 1163 // Source source = iterator.key.source; | |
| 1164 // if (source != null) { | |
| 1165 // CacheEntry entry = iterator.value; | |
| 1166 // if (entry.getState(result) == stateFilter) { | |
| 1167 // String link = makeLink(CACHE_ENTRY_PATH, { | |
| 1168 // CONTEXT_QUERY_PARAM: folder.path, | |
| 1169 // SOURCE_QUERY_PARAM: source.uri.toString() | |
| 1170 // }, HTML_ESCAPE.convert(source.fullName)); | |
| 1171 // links.add(link); | |
| 1172 // } | |
| 1173 // } | |
| 1174 // } | |
| 1175 | |
| 1176 _writeResponse(request, (StringBuffer buffer) { | |
| 1177 _writePage(buffer, 'Analysis Server - Cache Search', [ | |
| 1178 'Context: $contextFilter', | |
| 1179 'Descriptor: ${HTML_ESCAPE.convert(descriptorFilter)}', | |
| 1180 'State: ${HTML_ESCAPE.convert(stateQueryParam)}' | |
| 1181 ], (StringBuffer buffer) { | |
| 1182 buffer.write('<p>Cache search is not yet implemented.</p>'); | |
| 1183 // buffer.write('<p>${links.length} files found</p>'); | |
| 1184 // buffer.write('<ul>'); | |
| 1185 // links.forEach((String link) { | |
| 1186 // buffer.write('<li>$link</li>'); | |
| 1187 // }); | |
| 1188 // buffer.write('</ul>'); | |
| 1189 }); | |
| 1190 }); | |
| 1191 } | |
| 1192 | |
| 1193 /** | |
| 1194 * Return a response displaying overall performance information. | |
| 1195 */ | |
| 1196 void _returnCommunicationPerformance(HttpRequest request) { | |
| 1197 AnalysisServer analysisServer = _server.analysisServer; | |
| 1198 if (analysisServer == null) { | |
| 1199 return _returnFailure(request, 'Analysis server is not running'); | |
| 1200 } | |
| 1201 _writeResponse(request, (StringBuffer buffer) { | |
| 1202 _writePage(buffer, 'Analysis Server - Communication Performance', [], | |
| 1203 (StringBuffer buffer) { | |
| 1204 buffer.write('<h3>Communication Performance</h3>'); | |
| 1205 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 1206 ServerPerformance perf = analysisServer.performanceDuringStartup; | |
| 1207 int requestCount = perf.requestCount; | |
| 1208 num averageLatency = requestCount > 0 | |
| 1209 ? (perf.requestLatency / requestCount).round() | |
| 1210 : 0; | |
| 1211 int maximumLatency = perf.maxLatency; | |
| 1212 num slowRequestPercent = requestCount > 0 | |
| 1213 ? (perf.slowRequestCount * 100 / requestCount).round() | |
| 1214 : 0; | |
| 1215 buffer.write('<h4>Startup</h4>'); | |
| 1216 buffer.write('<table>'); | |
| 1217 _writeRow(buffer, [requestCount, 'requests'], | |
| 1218 classes: ["right", null]); | |
| 1219 _writeRow(buffer, [averageLatency, 'ms average latency'], | |
| 1220 classes: ["right", null]); | |
| 1221 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | |
| 1222 classes: ["right", null]); | |
| 1223 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | |
| 1224 classes: ["right", null]); | |
| 1225 if (analysisServer.performanceAfterStartup != null) { | |
| 1226 int startupTime = analysisServer.performanceAfterStartup.startTime - | |
| 1227 perf.startTime; | |
| 1228 _writeRow( | |
| 1229 buffer, [startupTime, 'ms for initial analysis to complete']); | |
| 1230 } | |
| 1231 buffer.write('</table>'); | |
| 1232 }, (StringBuffer buffer) { | |
| 1233 ServerPerformance perf = analysisServer.performanceAfterStartup; | |
| 1234 if (perf == null) { | |
| 1235 return; | |
| 1236 } | |
| 1237 int requestCount = perf.requestCount; | |
| 1238 num averageLatency = requestCount > 0 | |
| 1239 ? (perf.requestLatency * 10 / requestCount).round() / 10 | |
| 1240 : 0; | |
| 1241 int maximumLatency = perf.maxLatency; | |
| 1242 num slowRequestPercent = requestCount > 0 | |
| 1243 ? (perf.slowRequestCount * 100 / requestCount).round() | |
| 1244 : 0; | |
| 1245 buffer.write('<h4>Current</h4>'); | |
| 1246 buffer.write('<table>'); | |
| 1247 _writeRow(buffer, [requestCount, 'requests'], | |
| 1248 classes: ["right", null]); | |
| 1249 _writeRow(buffer, [averageLatency, 'ms average latency'], | |
| 1250 classes: ["right", null]); | |
| 1251 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | |
| 1252 classes: ["right", null]); | |
| 1253 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | |
| 1254 classes: ["right", null]); | |
| 1255 buffer.write('</table>'); | |
| 1256 }); | |
| 1257 }); | |
| 1258 }); | |
| 1259 } | |
| 1260 | |
| 1261 /** | |
| 1262 * Return a response displaying code completion information. | |
| 1263 */ | |
| 1264 void _returnCompletionInfo(HttpRequest request) { | |
| 1265 String value = request.requestedUri.queryParameters['index']; | |
| 1266 int index = value != null ? int.parse(value, onError: (_) => 0) : 0; | |
| 1267 _writeResponse(request, (StringBuffer buffer) { | |
| 1268 _writePage(buffer, 'Analysis Server - Completion Stats', [], | |
| 1269 (StringBuffer buffer) { | |
| 1270 _writeCompletionPerformanceDetail(buffer, index); | |
| 1271 _writeCompletionPerformanceList(buffer); | |
| 1272 }); | |
| 1273 }); | |
| 1274 } | |
| 1275 | |
| 1276 /** | |
| 1277 * Return a response displaying diagnostic information for a single context. | |
| 1278 */ | |
| 1279 void _returnContextDiagnostics(HttpRequest request) { | |
| 1280 AnalysisServer analysisServer = _server.analysisServer; | |
| 1281 if (analysisServer == null) { | |
| 1282 return _returnFailure(request, 'Analysis server is not running'); | |
| 1283 } | |
| 1284 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 1285 if (contextFilter == null) { | |
| 1286 return _returnFailure( | |
| 1287 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 1288 } | |
| 1289 InternalAnalysisContext context = null; | |
| 1290 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 1291 if (folder == null) { | |
| 1292 context = _getSdkContext(analysisServer, contextFilter); | |
| 1293 if (context == null) { | |
| 1294 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 1295 } | |
| 1296 } else { | |
| 1297 context = analysisServer.folderMap[folder]; | |
| 1298 } | |
| 1299 | |
| 1300 _writeResponse(request, (StringBuffer buffer) { | |
| 1301 _writePage(buffer, 'Analysis Server - Context Diagnostics', | |
| 1302 ['Context: $contextFilter'], (StringBuffer buffer) { | |
| 1303 _writeContextDiagnostics(buffer, context, contextFilter); | |
| 1304 }); | |
| 1305 }); | |
| 1306 } | |
| 1307 | |
| 1308 /** | |
| 1309 * Return a response containing information about a single source file in the | |
| 1310 * cache. | |
| 1311 */ | |
| 1312 void _returnContextInfo(HttpRequest request) { | |
| 1313 AnalysisServer analysisServer = _server.analysisServer; | |
| 1314 if (analysisServer == null) { | |
| 1315 return _returnFailure(request, 'Analysis server not running'); | |
| 1316 } | |
| 1317 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 1318 if (contextFilter == null) { | |
| 1319 return _returnFailure( | |
| 1320 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 1321 } | |
| 1322 InternalAnalysisContext context = null; | |
| 1323 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 1324 if (folder == null) { | |
| 1325 context = _getSdkContext(analysisServer, contextFilter); | |
| 1326 if (context == null) { | |
| 1327 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 1328 } | |
| 1329 } else { | |
| 1330 context = analysisServer.folderMap[folder]; | |
| 1331 } | |
| 1332 | |
| 1333 List<String> priorityNames = <String>[]; | |
| 1334 List<String> explicitNames = <String>[]; | |
| 1335 List<String> implicitNames = <String>[]; | |
| 1336 Map<String, String> links = new HashMap<String, String>(); | |
| 1337 List<CaughtException> exceptions = <CaughtException>[]; | |
| 1338 context.prioritySources.forEach((Source source) { | |
| 1339 priorityNames.add(source.fullName); | |
| 1340 }); | |
| 1341 MapIterator<AnalysisTarget, CacheEntry> iterator = | |
| 1342 context.analysisCache.iterator(context: context); | |
| 1343 while (iterator.moveNext()) { | |
| 1344 AnalysisTarget target = iterator.key; | |
| 1345 if (target is Source) { | |
| 1346 CacheEntry entry = iterator.value; | |
| 1347 String sourceName = target.fullName; | |
| 1348 if (!links.containsKey(sourceName)) { | |
| 1349 CaughtException exception = entry.exception; | |
| 1350 if (exception != null) { | |
| 1351 exceptions.add(exception); | |
| 1352 } | |
| 1353 String link = makeLink( | |
| 1354 CACHE_ENTRY_PATH, | |
| 1355 { | |
| 1356 CONTEXT_QUERY_PARAM: contextFilter, | |
| 1357 SOURCE_QUERY_PARAM: target.uri.toString() | |
| 1358 }, | |
| 1359 sourceName, | |
| 1360 exception != null); | |
| 1361 if (entry.explicitlyAdded) { | |
| 1362 explicitNames.add(sourceName); | |
| 1363 } else { | |
| 1364 implicitNames.add(sourceName); | |
| 1365 } | |
| 1366 links[sourceName] = link; | |
| 1367 } | |
| 1368 } | |
| 1369 } | |
| 1370 explicitNames.sort(); | |
| 1371 implicitNames.sort(); | |
| 1372 | |
| 1373 _overlayContents.clear(); | |
| 1374 context.visitContentCache((String fullName, int stamp, String contents) { | |
| 1375 _overlayContents[fullName] = contents; | |
| 1376 }); | |
| 1377 | |
| 1378 void _writeFiles( | |
| 1379 StringBuffer buffer, String title, List<String> fileNames) { | |
| 1380 buffer.write('<h3>$title</h3>'); | |
| 1381 if (fileNames == null || fileNames.isEmpty) { | |
| 1382 buffer.write('<p>None</p>'); | |
| 1383 } else { | |
| 1384 buffer.write('<p><table style="width: 100%">'); | |
| 1385 for (String fileName in fileNames) { | |
| 1386 buffer.write('<tr><td>'); | |
| 1387 buffer.write(links[fileName]); | |
| 1388 buffer.write('</td><td>'); | |
| 1389 if (_overlayContents.containsKey(fileName)) { | |
| 1390 buffer.write( | |
| 1391 makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay')); | |
| 1392 } | |
| 1393 buffer.write('</td></tr>'); | |
| 1394 } | |
| 1395 buffer.write('</table></p>'); | |
| 1396 } | |
| 1397 } | |
| 1398 | |
| 1399 void writeOptions(StringBuffer buffer, AnalysisOptionsImpl options, | |
| 1400 {void writeAdditionalOptions(StringBuffer buffer)}) { | |
| 1401 if (options == null) { | |
| 1402 buffer.write('<p>No option information available.</p>'); | |
| 1403 return; | |
| 1404 } | |
| 1405 buffer.write('<p>'); | |
| 1406 _writeOption( | |
| 1407 buffer, 'Analyze functon bodies', options.analyzeFunctionBodies); | |
| 1408 _writeOption( | |
| 1409 buffer, 'Enable strict call checks', options.enableStrictCallChecks); | |
| 1410 _writeOption(buffer, 'Enable super mixins', options.enableSuperMixins); | |
| 1411 _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint); | |
| 1412 _writeOption(buffer, 'Generate errors in implicit files', | |
| 1413 options.generateImplicitErrors); | |
| 1414 _writeOption( | |
| 1415 buffer, 'Generate errors in SDK files', options.generateSdkErrors); | |
| 1416 _writeOption(buffer, 'Generate hints', options.hint); | |
| 1417 _writeOption(buffer, 'Incremental resolution', options.incremental); | |
| 1418 _writeOption(buffer, 'Incremental resolution with API changes', | |
| 1419 options.incrementalApi); | |
| 1420 _writeOption(buffer, 'Preserve comments', options.preserveComments); | |
| 1421 _writeOption(buffer, 'Strong mode', options.strongMode); | |
| 1422 _writeOption(buffer, 'Strong mode hints', options.strongModeHints); | |
| 1423 if (writeAdditionalOptions != null) { | |
| 1424 writeAdditionalOptions(buffer); | |
| 1425 } | |
| 1426 buffer.write('</p>'); | |
| 1427 } | |
| 1428 | |
| 1429 _writeResponse(request, (StringBuffer buffer) { | |
| 1430 _writePage( | |
| 1431 buffer, 'Analysis Server - Context', ['Context: $contextFilter'], | |
| 1432 (StringBuffer buffer) { | |
| 1433 buffer.write('<h3>Configuration</h3>'); | |
| 1434 | |
| 1435 _writeColumns(buffer, <HtmlGenerator>[ | |
| 1436 (StringBuffer buffer) { | |
| 1437 buffer.write('<p><b>Context Options</b></p>'); | |
| 1438 writeOptions(buffer, context.analysisOptions); | |
| 1439 }, | |
| 1440 (StringBuffer buffer) { | |
| 1441 buffer.write('<p><b>SDK Context Options</b></p>'); | |
| 1442 DartSdk sdk = context?.sourceFactory?.dartSdk; | |
| 1443 writeOptions(buffer, sdk?.context?.analysisOptions, | |
| 1444 writeAdditionalOptions: (StringBuffer buffer) { | |
| 1445 if (sdk is FolderBasedDartSdk) { | |
| 1446 _writeOption(buffer, 'Use summaries', sdk.useSummary); | |
| 1447 } | |
| 1448 }); | |
| 1449 }, | |
| 1450 (StringBuffer buffer) { | |
| 1451 List<Linter> lints = context.analysisOptions.lintRules; | |
| 1452 buffer.write('<p><b>Lints</b></p>'); | |
| 1453 if (lints.isEmpty) { | |
| 1454 buffer.write('<p>none</p>'); | |
| 1455 } else { | |
| 1456 for (Linter lint in lints) { | |
| 1457 buffer.write('<p>'); | |
| 1458 buffer.write(lint.runtimeType); | |
| 1459 buffer.write('</p>'); | |
| 1460 } | |
| 1461 } | |
| 1462 | |
| 1463 List<ErrorProcessor> errorProcessors = | |
| 1464 context.analysisOptions.errorProcessors; | |
| 1465 int processorCount = errorProcessors?.length ?? 0; | |
| 1466 buffer | |
| 1467 .write('<p><b>Error Processor count</b>: $processorCount</p>'); | |
| 1468 } | |
| 1469 ]); | |
| 1470 | |
| 1471 SourceFactory sourceFactory = context.sourceFactory; | |
| 1472 if (sourceFactory is SourceFactoryImpl) { | |
| 1473 buffer.write('<h3>Resolvers</h3>'); | |
| 1474 for (UriResolver resolver in sourceFactory.resolvers) { | |
| 1475 buffer.write('<p>'); | |
| 1476 buffer.write(resolver.runtimeType); | |
| 1477 if (resolver is DartUriResolver) { | |
| 1478 DartSdk sdk = resolver.dartSdk; | |
| 1479 buffer.write(' (sdk = '); | |
| 1480 buffer.write(sdk.runtimeType); | |
| 1481 if (sdk is FolderBasedDartSdk) { | |
| 1482 buffer.write(' (path = '); | |
| 1483 buffer.write(sdk.directory.path); | |
| 1484 buffer.write(')'); | |
| 1485 } else if (sdk is EmbedderSdk) { | |
| 1486 buffer.write(' (map = '); | |
| 1487 _writeMapOfStringToString(buffer, sdk.urlMappings); | |
| 1488 buffer.write(')'); | |
| 1489 } | |
| 1490 buffer.write(')'); | |
| 1491 } else if (resolver is SdkExtUriResolver) { | |
| 1492 buffer.write(' (map = '); | |
| 1493 _writeMapOfStringToString(buffer, resolver.urlMappings); | |
| 1494 buffer.write(')'); | |
| 1495 } | |
| 1496 buffer.write('</p>'); | |
| 1497 } | |
| 1498 } | |
| 1499 | |
| 1500 _writeFiles( | |
| 1501 buffer, 'Priority Files (${priorityNames.length})', priorityNames); | |
| 1502 _writeFiles( | |
| 1503 buffer, | |
| 1504 'Explicitly Analyzed Files (${explicitNames.length})', | |
| 1505 explicitNames); | |
| 1506 _writeFiles( | |
| 1507 buffer, | |
| 1508 'Implicitly Analyzed Files (${implicitNames.length})', | |
| 1509 implicitNames); | |
| 1510 | |
| 1511 buffer.write('<h3>Exceptions</h3>'); | |
| 1512 if (exceptions.isEmpty) { | |
| 1513 buffer.write('<p>none</p>'); | |
| 1514 } else { | |
| 1515 exceptions.forEach((CaughtException exception) { | |
| 1516 _writeException(buffer, exception); | |
| 1517 }); | |
| 1518 } | |
| 1519 | |
| 1520 buffer.write('<h3>Targets Without Entries</h3>'); | |
| 1521 bool foundEntry = false; | |
| 1522 MapIterator<AnalysisTarget, CacheEntry> iterator = | |
| 1523 context.analysisCache.iterator(context: context); | |
| 1524 while (iterator.moveNext()) { | |
| 1525 if (iterator.value == null) { | |
| 1526 foundEntry = true; | |
| 1527 buffer.write('<p>'); | |
| 1528 buffer.write(iterator.key.toString()); | |
| 1529 buffer.write(' ('); | |
| 1530 buffer.write(iterator.key.runtimeType.toString()); | |
| 1531 buffer.write(')</p>'); | |
| 1532 } | |
| 1533 } | |
| 1534 if (!foundEntry) { | |
| 1535 buffer.write('<p>none</p>'); | |
| 1536 } | |
| 1537 }); | |
| 1538 }); | |
| 1539 } | |
| 1540 | |
| 1541 /** | |
| 1542 * Return a response displaying the results of running a validation report on | |
| 1543 * a single context. | |
| 1544 */ | |
| 1545 void _returnContextValidationDiagnostics(HttpRequest request) { | |
| 1546 AnalysisServer analysisServer = _server.analysisServer; | |
| 1547 if (analysisServer == null) { | |
| 1548 return _returnFailure(request, 'Analysis server is not running'); | |
| 1549 } | |
| 1550 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 1551 if (contextFilter == null) { | |
| 1552 return _returnFailure( | |
| 1553 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 1554 } | |
| 1555 InternalAnalysisContext context = null; | |
| 1556 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 1557 if (folder == null) { | |
| 1558 context = _getSdkContext(analysisServer, contextFilter); | |
| 1559 if (context == null) { | |
| 1560 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 1561 } | |
| 1562 } else { | |
| 1563 context = analysisServer.folderMap[folder]; | |
| 1564 } | |
| 1565 | |
| 1566 _writeResponse(request, (StringBuffer buffer) { | |
| 1567 _writePage(buffer, 'Analysis Server - Context Validation Diagnostics', | |
| 1568 ['Context: $contextFilter'], (StringBuffer buffer) { | |
| 1569 _writeContextValidationDiagnostics(buffer, context); | |
| 1570 }); | |
| 1571 }); | |
| 1572 } | |
| 1573 | |
| 1574 /** | |
| 1575 * Return a response displaying diagnostic information. | |
| 1576 */ | |
| 1577 void _returnDiagnosticInfo(HttpRequest request) { | |
| 1578 _writeResponse(request, (StringBuffer buffer) { | |
| 1579 _writePage(buffer, 'Analysis Server - Diagnostic info', [], | |
| 1580 (StringBuffer buffer) { | |
| 1581 _writeDiagnosticStatus(buffer); | |
| 1582 }); | |
| 1583 }); | |
| 1584 } | |
| 1585 | |
| 1586 /** | |
| 1587 * Return a response containing information about an element structure. | |
| 1588 */ | |
| 1589 void _returnElement(HttpRequest request) { | |
| 1590 AnalysisServer analysisServer = _server.analysisServer; | |
| 1591 if (analysisServer == null) { | |
| 1592 return _returnFailure(request, 'Analysis server not running'); | |
| 1593 } | |
| 1594 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 1595 if (contextFilter == null) { | |
| 1596 return _returnFailure( | |
| 1597 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 1598 } | |
| 1599 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 1600 if (folder == null) { | |
| 1601 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 1602 } | |
| 1603 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; | |
| 1604 if (sourceUri == null) { | |
| 1605 return _returnFailure( | |
| 1606 request, 'Query parameter $SOURCE_QUERY_PARAM required'); | |
| 1607 } | |
| 1608 | |
| 1609 InternalAnalysisContext context = analysisServer.folderMap[folder]; | |
| 1610 | |
| 1611 _writeResponse(request, (StringBuffer buffer) { | |
| 1612 _writePage(buffer, 'Analysis Server - Element Model', [ | |
| 1613 'Context: $contextFilter', | |
| 1614 'File: $sourceUri' | |
| 1615 ], (StringBuffer buffer) { | |
| 1616 Source source = context.sourceFactory.forUri(sourceUri); | |
| 1617 if (source == null) { | |
| 1618 buffer.write('<p>Not found.</p>'); | |
| 1619 return; | |
| 1620 } | |
| 1621 CacheEntry entry = context.analysisCache.get(source); | |
| 1622 if (entry == null) { | |
| 1623 buffer.write('<p>Not found.</p>'); | |
| 1624 return; | |
| 1625 } | |
| 1626 LibraryElement element = entry.getValue(LIBRARY_ELEMENT); | |
| 1627 if (element == null) { | |
| 1628 buffer.write('<p>null</p>'); | |
| 1629 return; | |
| 1630 } | |
| 1631 element.accept(new ElementWriter(buffer)); | |
| 1632 }); | |
| 1633 }); | |
| 1634 } | |
| 1635 | |
| 1636 void _returnFailure(HttpRequest request, String message) { | |
| 1637 _writeResponse(request, (StringBuffer buffer) { | |
| 1638 _writePage(buffer, 'Analysis Server - Failure', [], | |
| 1639 (StringBuffer buffer) { | |
| 1640 buffer.write(HTML_ESCAPE.convert(message)); | |
| 1641 }); | |
| 1642 }); | |
| 1643 } | |
| 1644 | |
| 1645 void _returnMemoryUsage(HttpRequest request) { | |
| 1646 _writeResponse(request, (StringBuffer buffer) { | |
| 1647 _writePage(buffer, 'Analysis Server - Memory Use', [], | |
| 1648 (StringBuffer buffer) { | |
| 1649 AnalysisServer server = _server.analysisServer; | |
| 1650 MemoryUseData data = new MemoryUseData(); | |
| 1651 data.processAnalysisServer(server); | |
| 1652 Map<Type, Set> instances = data.instances; | |
| 1653 List<Type> instanceTypes = instances.keys.toList(); | |
| 1654 instanceTypes.sort((Type left, Type right) => | |
| 1655 left.toString().compareTo(right.toString())); | |
| 1656 Map<Type, Set> ownerMap = data.ownerMap; | |
| 1657 List<Type> ownerTypes = ownerMap.keys.toList(); | |
| 1658 ownerTypes.sort((Type left, Type right) => | |
| 1659 left.toString().compareTo(right.toString())); | |
| 1660 | |
| 1661 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 1662 buffer.write('<h3>Instance Counts (reachable from contexts)</h3>'); | |
| 1663 buffer.write('<table>'); | |
| 1664 _writeRow(buffer, ['Count', 'Class name'], header: true); | |
| 1665 instanceTypes.forEach((Type type) { | |
| 1666 _writeRow(buffer, [instances[type].length, type], | |
| 1667 classes: ['right', null]); | |
| 1668 }); | |
| 1669 buffer.write('</table>'); | |
| 1670 | |
| 1671 buffer.write( | |
| 1672 '<h3>Ownership (which classes of objects hold on to others)</h3>')
; | |
| 1673 buffer.write('<table>'); | |
| 1674 _writeRow(buffer, ['Referenced Type', 'Referencing Types'], | |
| 1675 header: true); | |
| 1676 ownerTypes.forEach((Type type) { | |
| 1677 List<String> referencingTypes = | |
| 1678 ownerMap[type].map((Type type) => type.toString()).toList(); | |
| 1679 referencingTypes.sort(); | |
| 1680 _writeRow(buffer, [type, referencingTypes.join('<br>')]); | |
| 1681 }); | |
| 1682 buffer.write('</table>'); | |
| 1683 | |
| 1684 buffer.write('<h3>Other Data</h3>'); | |
| 1685 buffer.write('<p>'); | |
| 1686 buffer.write(data.uniqueTargetedResults.length); | |
| 1687 buffer.write(' non-equal TargetedResults</p>'); | |
| 1688 buffer.write('<p>'); | |
| 1689 buffer.write(data.uniqueLSUs.length); | |
| 1690 buffer.write(' non-equal LibrarySpecificUnits</p>'); | |
| 1691 int count = data.mismatchedTargets.length; | |
| 1692 buffer.write('<p>'); | |
| 1693 buffer.write(count); | |
| 1694 buffer.write(' mismatched targets</p>'); | |
| 1695 if (count < 100) { | |
| 1696 for (AnalysisTarget target in data.mismatchedTargets) { | |
| 1697 buffer.write(target); | |
| 1698 buffer.write('<br>'); | |
| 1699 } | |
| 1700 } | |
| 1701 }, (StringBuffer buffer) { | |
| 1702 void writeCountMap(String title, Map<Type, int> counts) { | |
| 1703 List<Type> classNames = counts.keys.toList(); | |
| 1704 classNames.sort((Type left, Type right) => | |
| 1705 left.toString().compareTo(right.toString())); | |
| 1706 | |
| 1707 buffer.write('<h3>$title</h3>'); | |
| 1708 buffer.write('<table>'); | |
| 1709 _writeRow(buffer, ['Count', 'Class name'], header: true); | |
| 1710 classNames.forEach((Type type) { | |
| 1711 _writeRow(buffer, [counts[type], type], classes: ['right', null]); | |
| 1712 }); | |
| 1713 buffer.write('</table>'); | |
| 1714 } | |
| 1715 | |
| 1716 writeCountMap('Directly Held AST Nodes', data.directNodeCounts); | |
| 1717 writeCountMap('Indirectly Held AST Nodes', data.indirectNodeCounts); | |
| 1718 writeCountMap('Directly Held Elements', data.elementCounts); | |
| 1719 }); | |
| 1720 }); | |
| 1721 }); | |
| 1722 } | |
| 1723 | |
| 1724 void _returnOverlayContents(HttpRequest request) { | |
| 1725 String path = request.requestedUri.queryParameters[PATH_PARAM]; | |
| 1726 if (path == null) { | |
| 1727 return _returnFailure(request, 'Query parameter $PATH_PARAM required'); | |
| 1728 } | |
| 1729 String contents = _overlayContents[path]; | |
| 1730 | |
| 1731 _writeResponse(request, (StringBuffer buffer) { | |
| 1732 _writePage(buffer, 'Analysis Server - Overlay', [], | |
| 1733 (StringBuffer buffer) { | |
| 1734 buffer.write('<pre>${HTML_ESCAPE.convert(contents)}</pre>'); | |
| 1735 }); | |
| 1736 }); | |
| 1737 } | |
| 1738 | |
| 1739 /** | |
| 1740 * Return a response displaying overlays information. | |
| 1741 */ | |
| 1742 void _returnOverlaysInfo(HttpRequest request) { | |
| 1743 AnalysisServer analysisServer = _server.analysisServer; | |
| 1744 if (analysisServer == null) { | |
| 1745 return _returnFailure(request, 'Analysis server is not running'); | |
| 1746 } | |
| 1747 | |
| 1748 _writeResponse(request, (StringBuffer buffer) { | |
| 1749 _writePage(buffer, 'Analysis Server - Overlays', [], | |
| 1750 (StringBuffer buffer) { | |
| 1751 buffer.write('<table border="1">'); | |
| 1752 _overlayContents.clear(); | |
| 1753 ContentCache overlayState = analysisServer.overlayState; | |
| 1754 overlayState.accept((String fullName, int stamp, String contents) { | |
| 1755 buffer.write('<tr>'); | |
| 1756 String link = | |
| 1757 makeLink(OVERLAY_PATH, {PATH_PARAM: fullName}, fullName); | |
| 1758 DateTime time = new DateTime.fromMillisecondsSinceEpoch(stamp); | |
| 1759 _writeRow(buffer, [link, time]); | |
| 1760 _overlayContents[fullName] = contents; | |
| 1761 }); | |
| 1762 int count = _overlayContents.length; | |
| 1763 buffer.write('<tr><td colspan="2">Total: $count entries.</td></tr>'); | |
| 1764 buffer.write('</table>'); | |
| 1765 }); | |
| 1766 }); | |
| 1767 } | |
| 1768 | |
| 1769 /** | |
| 1770 * Return a response indicating the status of the analysis server. | |
| 1771 */ | |
| 1772 void _returnServerStatus(HttpRequest request) { | |
| 1773 _writeResponse(request, (StringBuffer buffer) { | |
| 1774 _writePage(buffer, 'Analysis Server - Status', [], (StringBuffer buffer) { | |
| 1775 if (_writeServerStatus(buffer)) { | |
| 1776 _writeAnalysisStatus(buffer); | |
| 1777 _writeEditStatus(buffer); | |
| 1778 _writeExecutionStatus(buffer); | |
| 1779 _writePluginStatus(buffer); | |
| 1780 _writeRecentOutput(buffer); | |
| 1781 } | |
| 1782 }); | |
| 1783 }); | |
| 1784 } | |
| 1785 | |
| 1786 /** | |
| 1787 * Return an error in response to an unrecognized request received by the HTTP | |
| 1788 * server. | |
| 1789 */ | |
| 1790 void _returnUnknownRequest(HttpRequest request) { | |
| 1791 _writeResponse(request, (StringBuffer buffer) { | |
| 1792 _writePage(buffer, 'Analysis Server', [], (StringBuffer buffer) { | |
| 1793 buffer.write('<h3>Unknown page: '); | |
| 1794 buffer.write(request.uri.path); | |
| 1795 buffer.write('</h3>'); | |
| 1796 buffer.write(''' | |
| 1797 <p> | |
| 1798 You have reached an un-recognized page. If you reached this page by | |
| 1799 following a link from a status page, please report the broken link to | |
| 1800 the Dart analyzer team: | |
| 1801 <a>https://github.com/dart-lang/sdk/issues/new</a>. | |
| 1802 </p><p> | |
| 1803 If you mistyped the URL, you can correct it or return to | |
| 1804 ${makeLink(STATUS_PATH, {}, 'the main status page')}. | |
| 1805 </p>'''); | |
| 1806 }); | |
| 1807 }); | |
| 1808 } | |
| 1809 | |
| 1810 /** | |
| 1811 * Return a two digit decimal representation of the given non-negative integer | |
| 1812 * [value]. | |
| 1813 */ | |
| 1814 String _twoDigit(int value) { | |
| 1815 if (value < 10) { | |
| 1816 return '0$value'; | |
| 1817 } | |
| 1818 return value.toString(); | |
| 1819 } | |
| 1820 | |
| 1821 /** | |
| 1822 * Write the status of the analysis domain (on the main status page) to the | |
| 1823 * given [buffer] object. | |
| 1824 */ | |
| 1825 void _writeAnalysisStatus(StringBuffer buffer) { | |
| 1826 AnalysisServer analysisServer = _server.analysisServer; | |
| 1827 Map<Folder, AnalysisContext> folderMap = analysisServer.folderMap; | |
| 1828 List<Folder> folders = folderMap.keys.toList(); | |
| 1829 folders.sort((Folder first, Folder second) => | |
| 1830 first.shortName.compareTo(second.shortName)); | |
| 1831 ServerOperationQueue operationQueue = analysisServer.operationQueue; | |
| 1832 | |
| 1833 buffer.write('<h3>Analysis Domain</h3>'); | |
| 1834 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 1835 if (operationQueue.isEmpty) { | |
| 1836 buffer.write('<p>Status: Done analyzing</p>'); | |
| 1837 } else { | |
| 1838 ServerOperation operation = operationQueue.peek(); | |
| 1839 if (operation is PerformAnalysisOperation) { | |
| 1840 Folder folder = _keyForValue(folderMap, operation.context); | |
| 1841 if (folder == null) { | |
| 1842 buffer.write('<p>Status: Analyzing in unmapped context</p>'); | |
| 1843 } else { | |
| 1844 buffer.write('<p>Status: Analyzing in ${folder.path}</p>'); | |
| 1845 } | |
| 1846 } else { | |
| 1847 buffer.write('<p>Status: Analyzing</p>'); | |
| 1848 } | |
| 1849 } | |
| 1850 buffer.write('<p>Using package resolver provider: '); | |
| 1851 buffer.write(_server.packageResolverProvider != null); | |
| 1852 buffer.write('</p>'); | |
| 1853 buffer.write('<p>'); | |
| 1854 buffer.write(makeLink(OVERLAYS_PATH, {}, 'All overlay information')); | |
| 1855 buffer.write('</p>'); | |
| 1856 | |
| 1857 buffer.write('<p><b>Analysis Contexts</b></p>'); | |
| 1858 buffer.write('<p>'); | |
| 1859 bool first = true; | |
| 1860 folders.forEach((Folder folder) { | |
| 1861 if (first) { | |
| 1862 first = false; | |
| 1863 } else { | |
| 1864 buffer.write('<br>'); | |
| 1865 } | |
| 1866 String key = folder.shortName; | |
| 1867 buffer.write(makeLink(CONTEXT_PATH, {CONTEXT_QUERY_PARAM: folder.path}, | |
| 1868 key, _hasException(folderMap[folder]))); | |
| 1869 buffer.write(' <small><b>['); | |
| 1870 buffer.write(makeLink(CONTEXT_DIAGNOSTICS_PATH, | |
| 1871 {CONTEXT_QUERY_PARAM: folder.path}, 'diagnostics')); | |
| 1872 buffer.write(']</b></small>'); | |
| 1873 if (!folder.getChild('.packages').exists) { | |
| 1874 buffer.write(' <small>[no .packages file]</small>'); | |
| 1875 } | |
| 1876 }); | |
| 1877 buffer.write('</p>'); | |
| 1878 buffer.write('<p><b>SDK Contexts</b></p>'); | |
| 1879 buffer.write('<p>'); | |
| 1880 first = true; | |
| 1881 DartSdkManager manager = analysisServer.sdkManager; | |
| 1882 List<SdkDescription> descriptors = manager.sdkDescriptors; | |
| 1883 if (descriptors.isEmpty) { | |
| 1884 buffer.write('none'); | |
| 1885 } else { | |
| 1886 Map<String, SdkDescription> sdkMap = <String, SdkDescription>{}; | |
| 1887 for (SdkDescription descriptor in descriptors) { | |
| 1888 sdkMap[descriptor.toString()] = descriptor; | |
| 1889 } | |
| 1890 List<String> descriptorNames = sdkMap.keys.toList(); | |
| 1891 descriptorNames.sort(); | |
| 1892 for (String name in descriptorNames) { | |
| 1893 if (first) { | |
| 1894 first = false; | |
| 1895 } else { | |
| 1896 buffer.write('<br>'); | |
| 1897 } | |
| 1898 SdkDescription descriptor = sdkMap[name]; | |
| 1899 String contextId = _encodeSdkDescriptor(descriptor); | |
| 1900 buffer.write(makeLink( | |
| 1901 CONTEXT_PATH, | |
| 1902 {CONTEXT_QUERY_PARAM: contextId}, | |
| 1903 name, | |
| 1904 _hasException(manager.getSdk(descriptor, () => null)?.context))); | |
| 1905 buffer.write(' <small><b>['); | |
| 1906 buffer.write(makeLink(CONTEXT_DIAGNOSTICS_PATH, | |
| 1907 {CONTEXT_QUERY_PARAM: contextId}, 'diagnostics')); | |
| 1908 buffer.write(']</b></small>'); | |
| 1909 } | |
| 1910 } | |
| 1911 buffer.write('</p>'); | |
| 1912 | |
| 1913 int freq = AnalysisServer.performOperationDelayFrequency; | |
| 1914 String delay = freq > 0 ? '1 ms every $freq ms' : 'off'; | |
| 1915 | |
| 1916 buffer.write('<p><b>Performance Data</b></p>'); | |
| 1917 buffer.write('<p>Perform operation delay: $delay</p>'); | |
| 1918 buffer.write('<p>'); | |
| 1919 buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data')); | |
| 1920 buffer.write('</p>'); | |
| 1921 }, (StringBuffer buffer) { | |
| 1922 _writeSubscriptionMap( | |
| 1923 buffer, AnalysisService.VALUES, analysisServer.analysisServices); | |
| 1924 }); | |
| 1925 } | |
| 1926 | |
| 1927 /** | |
| 1928 * Write multiple columns of information to the given [buffer], where the list | |
| 1929 * of [columns] functions are used to generate the content of those columns. | |
| 1930 */ | |
| 1931 void _writeColumns(StringBuffer buffer, List<HtmlGenerator> columns) { | |
| 1932 buffer | |
| 1933 .write('<table class="column"><tr class="column"><td class="column">'); | |
| 1934 int count = columns.length; | |
| 1935 for (int i = 0; i < count; i++) { | |
| 1936 if (i > 0) { | |
| 1937 buffer.write('</td><td class="column">'); | |
| 1938 } | |
| 1939 columns[i](buffer); | |
| 1940 } | |
| 1941 buffer.write('</td></tr></table>'); | |
| 1942 } | |
| 1943 | |
| 1944 /** | |
| 1945 * Write performance information about a specific completion request | |
| 1946 * to the given [buffer] object. | |
| 1947 */ | |
| 1948 void _writeCompletionPerformanceDetail(StringBuffer buffer, int index) { | |
| 1949 CompletionDomainHandler handler = _completionDomainHandler; | |
| 1950 CompletionPerformance performance; | |
| 1951 if (handler != null) { | |
| 1952 List<CompletionPerformance> list = handler.performanceList; | |
| 1953 if (list != null && list.isNotEmpty) { | |
| 1954 performance = list[max(0, min(list.length - 1, index))]; | |
| 1955 } | |
| 1956 } | |
| 1957 if (performance == null) { | |
| 1958 buffer.write('<h3>Completion Performance Detail</h3>'); | |
| 1959 buffer.write('<p>No completions yet</p>'); | |
| 1960 return; | |
| 1961 } | |
| 1962 buffer.write('<h3>Completion Performance Detail</h3>'); | |
| 1963 buffer.write('<p>${performance.startTimeAndMs} for ${performance.source}'); | |
| 1964 buffer.write('<table>'); | |
| 1965 _writeRow(buffer, ['Elapsed', '', 'Operation'], header: true); | |
| 1966 performance.operations.forEach((OperationPerformance op) { | |
| 1967 String elapsed = op.elapsed != null ? op.elapsed.toString() : '???'; | |
| 1968 _writeRow(buffer, [elapsed, ' ', op.name]); | |
| 1969 }); | |
| 1970 buffer.write('</table>'); | |
| 1971 buffer.write('<p><b>Compute Cache Performance</b>: '); | |
| 1972 if (handler.computeCachePerformance == null) { | |
| 1973 buffer.write('none'); | |
| 1974 } else { | |
| 1975 int elapsed = handler.computeCachePerformance.elapsedInMilliseconds; | |
| 1976 Source source = handler.computeCachePerformance.source; | |
| 1977 buffer.write(' $elapsed ms for $source'); | |
| 1978 } | |
| 1979 buffer.write('</p>'); | |
| 1980 } | |
| 1981 | |
| 1982 /** | |
| 1983 * Write a table showing summary information for the last several | |
| 1984 * completion requests to the given [buffer] object. | |
| 1985 */ | |
| 1986 void _writeCompletionPerformanceList(StringBuffer buffer) { | |
| 1987 CompletionDomainHandler handler = _completionDomainHandler; | |
| 1988 buffer.write('<h3>Completion Performance List</h3>'); | |
| 1989 if (handler == null) { | |
| 1990 return; | |
| 1991 } | |
| 1992 buffer.write('<table>'); | |
| 1993 _writeRow( | |
| 1994 buffer, | |
| 1995 [ | |
| 1996 'Start Time', | |
| 1997 '', | |
| 1998 'First (ms)', | |
| 1999 '', | |
| 2000 'Complete (ms)', | |
| 2001 '', | |
| 2002 '# Notifications', | |
| 2003 '', | |
| 2004 '# Suggestions', | |
| 2005 '', | |
| 2006 'Snippet' | |
| 2007 ], | |
| 2008 header: true); | |
| 2009 int index = 0; | |
| 2010 for (CompletionPerformance performance in handler.performanceList) { | |
| 2011 String link = makeLink(COMPLETION_PATH, {'index': '$index'}, | |
| 2012 '${performance.startTimeAndMs}'); | |
| 2013 _writeRow(buffer, [ | |
| 2014 link, | |
| 2015 ' ', | |
| 2016 performance.firstNotificationInMilliseconds, | |
| 2017 ' ', | |
| 2018 performance.elapsedInMilliseconds, | |
| 2019 ' ', | |
| 2020 performance.notificationCount, | |
| 2021 ' ', | |
| 2022 performance.suggestionCount, | |
| 2023 ' ', | |
| 2024 HTML_ESCAPE.convert(performance.snippet) | |
| 2025 ]); | |
| 2026 ++index; | |
| 2027 } | |
| 2028 | |
| 2029 buffer.write('</table>'); | |
| 2030 buffer.write(''' | |
| 2031 <p><strong>First (ms)</strong> - the number of milliseconds | |
| 2032 from when completion received the request until the first notification | |
| 2033 with completion results was queued for sending back to the client. | |
| 2034 <p><strong>Complete (ms)</strong> - the number of milliseconds | |
| 2035 from when completion received the request until the final notification | |
| 2036 with completion results was queued for sending back to the client. | |
| 2037 <p><strong># Notifications</strong> - the total number of notifications | |
| 2038 sent to the client with completion results for this request. | |
| 2039 <p><strong># Suggestions</strong> - the number of suggestions | |
| 2040 sent to the client in the first notification, followed by a comma, | |
| 2041 followed by the number of suggestions send to the client | |
| 2042 in the last notification. If there is only one notification, | |
| 2043 then there will be only one number in this column.'''); | |
| 2044 } | |
| 2045 | |
| 2046 /** | |
| 2047 * Write diagnostic information about the given [context] to the given | |
| 2048 * [buffer]. | |
| 2049 */ | |
| 2050 void _writeContextDiagnostics(StringBuffer buffer, | |
| 2051 InternalAnalysisContext context, String contextFilter) { | |
| 2052 AnalysisDriver driver = (context as AnalysisContextImpl).driver; | |
| 2053 List<WorkItem> workItems = driver.currentWorkOrder?.workItems; | |
| 2054 | |
| 2055 buffer.write('<p>'); | |
| 2056 buffer.write(makeLink(CONTEXT_VALIDATION_DIAGNOSTICS_PATH, | |
| 2057 {CONTEXT_QUERY_PARAM: contextFilter}, 'Run validation')); | |
| 2058 buffer.write('</p>'); | |
| 2059 | |
| 2060 buffer.write('<h3>Most Recently Perfomed Tasks</h3>'); | |
| 2061 AnalysisTask.LAST_TASKS.forEach((String description) { | |
| 2062 buffer.write('<p>'); | |
| 2063 buffer.write(description); | |
| 2064 buffer.write('</p>'); | |
| 2065 }); | |
| 2066 | |
| 2067 void writeWorkItem(StringBuffer buffer, WorkItem item) { | |
| 2068 if (item == null) { | |
| 2069 buffer.write('none'); | |
| 2070 } else { | |
| 2071 buffer.write(item.descriptor?.name); | |
| 2072 buffer.write(' computing '); | |
| 2073 buffer.write(item.spawningResult?.name); | |
| 2074 buffer.write(' for '); | |
| 2075 buffer.write(item.target); | |
| 2076 } | |
| 2077 } | |
| 2078 | |
| 2079 buffer.write('<h3>Work Items</h3>'); | |
| 2080 buffer.write('<p><b>Current:</b> '); | |
| 2081 writeWorkItem(buffer, driver.currentWorkOrder?.current); | |
| 2082 buffer.write('</p>'); | |
| 2083 if (workItems != null) { | |
| 2084 workItems.reversed.forEach((WorkItem item) { | |
| 2085 buffer.write('<p>'); | |
| 2086 writeWorkItem(buffer, item); | |
| 2087 buffer.write('</p>'); | |
| 2088 }); | |
| 2089 } | |
| 2090 } | |
| 2091 | |
| 2092 /** | |
| 2093 * Write diagnostic information about the given [context] to the given | |
| 2094 * [buffer]. | |
| 2095 */ | |
| 2096 void _writeContextValidationDiagnostics( | |
| 2097 StringBuffer buffer, InternalAnalysisContext context) { | |
| 2098 Stopwatch stopwatch = new Stopwatch(); | |
| 2099 stopwatch.start(); | |
| 2100 ValidationResults results = new ValidationResults(context); | |
| 2101 stopwatch.stop(); | |
| 2102 | |
| 2103 buffer.write('<h3>Validation Results</h3>'); | |
| 2104 buffer.write('<p>Re-analysis took '); | |
| 2105 buffer.write(stopwatch.elapsedMilliseconds); | |
| 2106 buffer.write(' ms</p>'); | |
| 2107 results.writeOn(buffer); | |
| 2108 } | |
| 2109 | |
| 2110 /** | |
| 2111 * Write the status of the diagnostic domain to the given [buffer]. | |
| 2112 */ | |
| 2113 void _writeDiagnosticStatus(StringBuffer buffer) { | |
| 2114 var request = new DiagnosticGetDiagnosticsParams().toRequest('0'); | |
| 2115 | |
| 2116 var stopwatch = new Stopwatch(); | |
| 2117 stopwatch.start(); | |
| 2118 var response = diagnosticHandler.handleRequest(request); | |
| 2119 stopwatch.stop(); | |
| 2120 | |
| 2121 int elapsedMs = stopwatch.elapsedMilliseconds; | |
| 2122 _diagnosticCallAverage.addSample(elapsedMs); | |
| 2123 | |
| 2124 buffer.write('<h3>Timing</h3>'); | |
| 2125 | |
| 2126 buffer.write('<p>getDiagnostic (last call): '); | |
| 2127 buffer.write(elapsedMs); | |
| 2128 buffer.write(' (ms)</p>'); | |
| 2129 buffer.write('<p>getDiagnostic (rolling average): '); | |
| 2130 buffer.write(_diagnosticCallAverage.value); | |
| 2131 buffer.write(' (ms)</p> '); | |
| 2132 | |
| 2133 Map json = response.toJson()[Response.RESULT]; | |
| 2134 List contexts = json['contexts']; | |
| 2135 contexts.sort((first, second) => first['name'].compareTo(second['name'])); | |
| 2136 | |
| 2137 // Track visited libraries. | |
| 2138 Set<LibraryElement> libraries = new HashSet<LibraryElement>(); | |
| 2139 | |
| 2140 // Count SDK elements separately. | |
| 2141 ElementCounter sdkCounter = new ElementCounter(); | |
| 2142 | |
| 2143 for (var context in contexts) { | |
| 2144 buffer.write('<p><h3>'); | |
| 2145 buffer.write(context['name']); | |
| 2146 buffer.write('</h3></p>'); | |
| 2147 buffer.write('<p>explicitFileCount: '); | |
| 2148 buffer.write(context['explicitFileCount']); | |
| 2149 buffer.write('</p>'); | |
| 2150 buffer.write('<p>implicitFileCount: '); | |
| 2151 buffer.write(context['implicitFileCount']); | |
| 2152 buffer.write('</p>'); | |
| 2153 buffer.write('<p>workItemQueueLength: '); | |
| 2154 buffer.write(context['workItemQueueLength']); | |
| 2155 buffer.write('</p>'); | |
| 2156 | |
| 2157 AnalysisServer server = _server.analysisServer; | |
| 2158 | |
| 2159 if (server != null) { | |
| 2160 Folder folder = _findFolder(server, context['name']); | |
| 2161 InternalAnalysisContext ac = _server.analysisServer.folderMap[folder]; | |
| 2162 ElementCounter counter = new ElementCounter(); | |
| 2163 | |
| 2164 for (Source source in ac.librarySources) { | |
| 2165 LibraryElement libraryElement = ac.getLibraryElement(source); | |
| 2166 if (libraries.add(libraryElement)) { | |
| 2167 if (libraryElement != null) { | |
| 2168 if (libraryElement.isInSdk) { | |
| 2169 libraryElement.accept(sdkCounter); | |
| 2170 } else { | |
| 2171 libraryElement.accept(counter); | |
| 2172 } | |
| 2173 } | |
| 2174 } | |
| 2175 } | |
| 2176 buffer.write('<p>element count: '); | |
| 2177 buffer.write(counter.counts.values | |
| 2178 .fold<int>(0, (int prev, int element) => prev + element)); | |
| 2179 buffer.write('</p>'); | |
| 2180 buffer.write('<p> (w/docs): '); | |
| 2181 buffer.write(counter.elementsWithDocs); | |
| 2182 buffer.write('</p>'); | |
| 2183 buffer.write('<p>total doc span: '); | |
| 2184 buffer.write(counter.totalDocSpan); | |
| 2185 buffer.write('</p>'); | |
| 2186 } | |
| 2187 } | |
| 2188 | |
| 2189 buffer.write('<p><h3>'); | |
| 2190 buffer.write('SDK'); | |
| 2191 buffer.write('</h3></p>'); | |
| 2192 buffer.write('<p>element count: '); | |
| 2193 buffer.write(sdkCounter.counts.values | |
| 2194 .fold<int>(0, (int prev, int element) => prev + element)); | |
| 2195 buffer.write('</p>'); | |
| 2196 buffer.write('<p> (w/docs): '); | |
| 2197 buffer.write(sdkCounter.elementsWithDocs); | |
| 2198 buffer.write('</p>'); | |
| 2199 buffer.write('<p>total doc span: '); | |
| 2200 buffer.write(sdkCounter.totalDocSpan); | |
| 2201 buffer.write('</p>'); | |
| 2202 } | |
| 2203 | |
| 2204 /** | |
| 2205 * Write the status of the edit domain (on the main status page) to the given | |
| 2206 * [buffer]. | |
| 2207 */ | |
| 2208 void _writeEditStatus(StringBuffer buffer) { | |
| 2209 buffer.write('<h3>Edit Domain</h3>'); | |
| 2210 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 2211 buffer.write('<p><b>Performance Data</b></p>'); | |
| 2212 buffer.write('<p>'); | |
| 2213 buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data')); | |
| 2214 buffer.write('</p>'); | |
| 2215 }, (StringBuffer buffer) {}); | |
| 2216 } | |
| 2217 | |
| 2218 /** | |
| 2219 * Write a representation of the given [caughtException] to the given | |
| 2220 * [buffer]. If [isCause] is `true`, then the exception was a cause for | |
| 2221 * another exception. | |
| 2222 */ | |
| 2223 void _writeException(StringBuffer buffer, CaughtException caughtException, | |
| 2224 {bool isCause: false}) { | |
| 2225 Object exception = caughtException.exception; | |
| 2226 | |
| 2227 if (exception is AnalysisException) { | |
| 2228 buffer.write('<p>'); | |
| 2229 if (isCause) { | |
| 2230 buffer.write('Caused by '); | |
| 2231 } | |
| 2232 buffer.write(exception.message); | |
| 2233 buffer.write('</p>'); | |
| 2234 _writeStackTrace(buffer, caughtException.stackTrace); | |
| 2235 CaughtException cause = exception.cause; | |
| 2236 if (cause != null) { | |
| 2237 buffer.write('<blockquote>'); | |
| 2238 _writeException(buffer, cause, isCause: true); | |
| 2239 buffer.write('</blockquote>'); | |
| 2240 } | |
| 2241 } else { | |
| 2242 buffer.write('<p>'); | |
| 2243 if (isCause) { | |
| 2244 buffer.write('Caused by '); | |
| 2245 } | |
| 2246 buffer.write(exception.toString()); | |
| 2247 buffer.write('<p>'); | |
| 2248 _writeStackTrace(buffer, caughtException.stackTrace); | |
| 2249 } | |
| 2250 } | |
| 2251 | |
| 2252 /** | |
| 2253 * Write the status of the execution domain (on the main status page) to the | |
| 2254 * given [buffer]. | |
| 2255 */ | |
| 2256 void _writeExecutionStatus(StringBuffer buffer) { | |
| 2257 AnalysisServer analysisServer = _server.analysisServer; | |
| 2258 ExecutionDomainHandler handler = analysisServer.handlers.firstWhere( | |
| 2259 (RequestHandler handler) => handler is ExecutionDomainHandler, | |
| 2260 orElse: () => null); | |
| 2261 Set<ExecutionService> services = new Set<ExecutionService>(); | |
| 2262 if (handler.onFileAnalyzed != null) { | |
| 2263 services.add(ExecutionService.LAUNCH_DATA); | |
| 2264 } | |
| 2265 | |
| 2266 if (handler != null) { | |
| 2267 buffer.write('<h3>Execution Domain</h3>'); | |
| 2268 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 2269 _writeSubscriptionList(buffer, ExecutionService.VALUES, services); | |
| 2270 }, (StringBuffer buffer) {}); | |
| 2271 } | |
| 2272 } | |
| 2273 | |
| 2274 void _writeListItem(StringBuffer buffer, writer()) { | |
| 2275 buffer.write('<li>'); | |
| 2276 writer(); | |
| 2277 buffer.write('</li>'); | |
| 2278 } | |
| 2279 | |
| 2280 void _writeListOfStrings( | |
| 2281 StringBuffer buffer, String listName, Iterable<String> items) { | |
| 2282 List<String> itemList = items.toList(); | |
| 2283 itemList.sort((String a, String b) { | |
| 2284 a = a.toLowerCase(); | |
| 2285 b = b.toLowerCase(); | |
| 2286 return a.compareTo(b); | |
| 2287 }); | |
| 2288 buffer.write('List "$listName" containing ${itemList.length} entries:'); | |
| 2289 buffer.write('<ul>'); | |
| 2290 for (String member in itemList) { | |
| 2291 _writeListItem(buffer, () { | |
| 2292 buffer.write(member); | |
| 2293 }); | |
| 2294 } | |
| 2295 buffer.write('</ul>'); | |
| 2296 } | |
| 2297 | |
| 2298 /** | |
| 2299 * Write to the given [buffer] a representation of the given [map] of strings | |
| 2300 * to strings. | |
| 2301 */ | |
| 2302 void _writeMapOfStringToString(StringBuffer buffer, Map<String, String> map) { | |
| 2303 List<String> keys = map.keys.toList(); | |
| 2304 keys.sort(); | |
| 2305 int length = keys.length; | |
| 2306 buffer.write('{'); | |
| 2307 for (int i = 0; i < length; i++) { | |
| 2308 buffer.write('<br>'); | |
| 2309 String key = keys[i]; | |
| 2310 if (i > 0) { | |
| 2311 buffer.write(', '); | |
| 2312 } | |
| 2313 buffer.write(key); | |
| 2314 buffer.write(' = '); | |
| 2315 buffer.write(map[key]); | |
| 2316 } | |
| 2317 buffer.write('<br>}'); | |
| 2318 } | |
| 2319 | |
| 2320 /** | |
| 2321 * Write a representation of an analysis option with the given [name] and | |
| 2322 * [value] to the given [buffer]. The option should be separated from other | |
| 2323 * options unless the [last] flag is true, indicating that this is the last | |
| 2324 * option in the list of options. | |
| 2325 */ | |
| 2326 void _writeOption(StringBuffer buffer, String name, Object value, | |
| 2327 {bool last: false}) { | |
| 2328 buffer.write(name); | |
| 2329 buffer.write(' = '); | |
| 2330 buffer.write(value.toString()); | |
| 2331 if (!last) { | |
| 2332 buffer.write('<br>'); | |
| 2333 } | |
| 2334 } | |
| 2335 | |
| 2336 /** | |
| 2337 * Write a standard HTML page to the given [buffer]. The page will have the | |
| 2338 * given [title] and a body that is generated by the given [body] generator. | |
| 2339 */ | |
| 2340 void _writePage(StringBuffer buffer, String title, List<String> subtitles, | |
| 2341 HtmlGenerator body) { | |
| 2342 DateTime now = new DateTime.now(); | |
| 2343 String date = "${now.month}/${now.day}/${now.year}"; | |
| 2344 String time = | |
| 2345 "${now.hour}:${_twoDigit(now.minute)}:${_twoDigit(now.second)}.${now.mil
lisecond}"; | |
| 2346 | |
| 2347 buffer.write('<!DOCTYPE html>'); | |
| 2348 buffer.write('<html>'); | |
| 2349 buffer.write('<head>'); | |
| 2350 buffer.write('<meta charset="utf-8">'); | |
| 2351 buffer.write( | |
| 2352 '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
); | |
| 2353 buffer.write('<title>$title</title>'); | |
| 2354 buffer.write('<style>'); | |
| 2355 buffer.write('a {color: #0000DD; text-decoration: none;}'); | |
| 2356 buffer.write('a:link.error {background-color: #FFEEEE;}'); | |
| 2357 buffer.write('a:visited.error {background-color: #FFEEEE;}'); | |
| 2358 buffer.write('a:hover.error {background-color: #FFEEEE;}'); | |
| 2359 buffer.write('a:active.error {background-color: #FFEEEE;}'); | |
| 2360 buffer.write( | |
| 2361 'h3 {background-color: #DDDDDD; margin-top: 0em; margin-bottom: 0em;}'); | |
| 2362 buffer.write('p {margin-top: 0.5em; margin-bottom: 0.5em;}'); | |
| 2363 buffer.write( | |
| 2364 'p.commentary {margin-top: 1em; margin-bottom: 1em; margin-left: 2em; fo
nt-style: italic;}'); | |
| 2365 // response.write('span.error {text-decoration-line: underline; text-decorati
on-color: red; text-decoration-style: wavy;}'); | |
| 2366 buffer.write( | |
| 2367 'table.column {border: 0px solid black; width: 100%; table-layout: fixed
;}'); | |
| 2368 buffer.write('td.column {vertical-align: top; width: 50%;}'); | |
| 2369 buffer.write('td.right {text-align: right;}'); | |
| 2370 buffer.write('th {text-align: left; vertical-align:top;}'); | |
| 2371 buffer.write('tr {vertical-align:top;}'); | |
| 2372 buffer.write('</style>'); | |
| 2373 buffer.write('</head>'); | |
| 2374 | |
| 2375 buffer.write('<body>'); | |
| 2376 buffer.write( | |
| 2377 '<h2>$title <small><small>(as of $time on $date)</small></small></h2>'); | |
| 2378 if (subtitles != null && subtitles.isNotEmpty) { | |
| 2379 buffer.write('<blockquote>'); | |
| 2380 bool first = true; | |
| 2381 for (String subtitle in subtitles) { | |
| 2382 if (first) { | |
| 2383 first = false; | |
| 2384 } else { | |
| 2385 buffer.write('<br>'); | |
| 2386 } | |
| 2387 buffer.write('<b>'); | |
| 2388 buffer.write(subtitle); | |
| 2389 buffer.write('</b>'); | |
| 2390 } | |
| 2391 buffer.write('</blockquote>'); | |
| 2392 } | |
| 2393 try { | |
| 2394 body(buffer); | |
| 2395 } catch (exception, stackTrace) { | |
| 2396 buffer.write('<h3>Exception while creating page</h3>'); | |
| 2397 _writeException(buffer, new CaughtException(exception, stackTrace)); | |
| 2398 } | |
| 2399 buffer.write('</body>'); | |
| 2400 buffer.write('</html>'); | |
| 2401 } | |
| 2402 | |
| 2403 /** | |
| 2404 * Write the recent output section (on the main status page) to the given | |
| 2405 * [buffer] object. | |
| 2406 */ | |
| 2407 void _writePluginStatus(StringBuffer buffer) { | |
| 2408 void writePlugin(Plugin plugin) { | |
| 2409 buffer.write(plugin.uniqueIdentifier); | |
| 2410 buffer.write(' ('); | |
| 2411 buffer.write(plugin.runtimeType); | |
| 2412 buffer.write(')<br>'); | |
| 2413 } | |
| 2414 | |
| 2415 buffer.write('<h3>Plugin Status</h3><p>'); | |
| 2416 writePlugin(AnalysisEngine.instance.enginePlugin); | |
| 2417 writePlugin(_server.serverPlugin); | |
| 2418 for (Plugin plugin in _server.analysisServer.userDefinedPlugins) { | |
| 2419 writePlugin(plugin); | |
| 2420 } | |
| 2421 buffer.write('<p>'); | |
| 2422 } | |
| 2423 | |
| 2424 /** | |
| 2425 * Write the recent output section (on the main status page) to the given | |
| 2426 * [buffer] object. | |
| 2427 */ | |
| 2428 void _writeRecentOutput(StringBuffer buffer) { | |
| 2429 buffer.write('<h3>Recent Output</h3>'); | |
| 2430 String output = HTML_ESCAPE.convert(_printBuffer.join('\n')); | |
| 2431 if (output.isEmpty) { | |
| 2432 buffer.write('<i>none</i>'); | |
| 2433 } else { | |
| 2434 buffer.write('<pre>'); | |
| 2435 buffer.write(output); | |
| 2436 buffer.write('</pre>'); | |
| 2437 } | |
| 2438 } | |
| 2439 | |
| 2440 void _writeResponse(HttpRequest request, HtmlGenerator writePage) { | |
| 2441 HttpResponse response = request.response; | |
| 2442 response.statusCode = HttpStatus.OK; | |
| 2443 response.headers.contentType = _htmlContent; | |
| 2444 try { | |
| 2445 StringBuffer buffer = new StringBuffer(); | |
| 2446 try { | |
| 2447 writePage(buffer); | |
| 2448 } catch (exception, stackTrace) { | |
| 2449 buffer.clear(); | |
| 2450 _writePage(buffer, 'Internal Exception', [], (StringBuffer buffer) { | |
| 2451 _writeException(buffer, new CaughtException(exception, stackTrace)); | |
| 2452 }); | |
| 2453 } | |
| 2454 response.write(buffer.toString()); | |
| 2455 } finally { | |
| 2456 response.close(); | |
| 2457 } | |
| 2458 } | |
| 2459 | |
| 2460 /** | |
| 2461 * Write a single row within a table to the given [buffer]. The row will have | |
| 2462 * one cell for each of the [columns], and will be a header row if [header] is | |
| 2463 * `true`. | |
| 2464 */ | |
| 2465 void _writeRow(StringBuffer buffer, List<Object> columns, | |
| 2466 {bool header: false, List<String> classes}) { | |
| 2467 buffer.write('<tr>'); | |
| 2468 int count = columns.length; | |
| 2469 int maxClassIndex = classes == null ? 0 : classes.length - 1; | |
| 2470 for (int i = 0; i < count; i++) { | |
| 2471 String classAttribute = ''; | |
| 2472 if (classes != null) { | |
| 2473 String className = classes[min(i, maxClassIndex)]; | |
| 2474 if (className != null) { | |
| 2475 classAttribute = ' class="$className"'; | |
| 2476 } | |
| 2477 } | |
| 2478 if (header) { | |
| 2479 buffer.write('<th$classAttribute>'); | |
| 2480 } else { | |
| 2481 buffer.write('<td$classAttribute>'); | |
| 2482 } | |
| 2483 buffer.write(columns[i]); | |
| 2484 if (header) { | |
| 2485 buffer.write('</th>'); | |
| 2486 } else { | |
| 2487 buffer.write('</td>'); | |
| 2488 } | |
| 2489 } | |
| 2490 buffer.write('</tr>'); | |
| 2491 } | |
| 2492 | |
| 2493 /** | |
| 2494 * Write the status of the service domain (on the main status page) to the | |
| 2495 * given [response] object. | |
| 2496 */ | |
| 2497 bool _writeServerStatus(StringBuffer buffer) { | |
| 2498 AnalysisServer analysisServer = _server.analysisServer; | |
| 2499 Set<ServerService> services = analysisServer.serverServices; | |
| 2500 | |
| 2501 buffer.write('<h3>Server Domain</h3>'); | |
| 2502 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 2503 if (analysisServer == null) { | |
| 2504 buffer.write('Status: <span style="color:red">Not running</span>'); | |
| 2505 return; | |
| 2506 } | |
| 2507 buffer.write('<p>'); | |
| 2508 buffer.write('Status: Running<br>'); | |
| 2509 buffer.write('New analysis driver: '); | |
| 2510 buffer.write(analysisServer.options.enableNewAnalysisDriver); | |
| 2511 buffer.write('<br>'); | |
| 2512 buffer.write('Instrumentation: '); | |
| 2513 if (AnalysisEngine.instance.instrumentationService.isActive) { | |
| 2514 buffer.write('<span style="color:red">Active</span>'); | |
| 2515 } else { | |
| 2516 buffer.write('Inactive'); | |
| 2517 } | |
| 2518 buffer.write('<br>'); | |
| 2519 buffer.write('Server version: '); | |
| 2520 buffer.write(AnalysisServer.VERSION); | |
| 2521 buffer.write('<br>'); | |
| 2522 buffer.write('SDK: '); | |
| 2523 buffer.write(Platform.version); | |
| 2524 buffer.write('<br>'); | |
| 2525 buffer.write('Process ID: '); | |
| 2526 buffer.write(pid); | |
| 2527 buffer.write('</p>'); | |
| 2528 | |
| 2529 buffer.write('<p><b>Performance Data</b></p>'); | |
| 2530 buffer.write('<p>'); | |
| 2531 buffer.write(makeLink( | |
| 2532 COMMUNICATION_PERFORMANCE_PATH, {}, 'Communication performance')); | |
| 2533 buffer.write('</p>'); | |
| 2534 buffer.write('<p>'); | |
| 2535 buffer.write(makeLink(DIAGNOSTIC_PATH, {}, 'General diagnostics')); | |
| 2536 buffer.write('</p>'); | |
| 2537 buffer.write('<p>'); | |
| 2538 buffer.write(makeLink(MEMORY_USE_PATH, {}, 'Memory usage')); | |
| 2539 buffer.write(' <small>(long running)</small></p>'); | |
| 2540 }, (StringBuffer buffer) { | |
| 2541 _writeSubscriptionList(buffer, ServerService.VALUES, services); | |
| 2542 }); | |
| 2543 return analysisServer != null; | |
| 2544 } | |
| 2545 | |
| 2546 /** | |
| 2547 * Write a representation of the given [stackTrace] to the given [buffer]. | |
| 2548 */ | |
| 2549 void _writeStackTrace(StringBuffer buffer, StackTrace stackTrace) { | |
| 2550 if (stackTrace != null) { | |
| 2551 String trace = stackTrace.toString().replaceAll('#', '<br>#'); | |
| 2552 if (trace.startsWith('<br>#')) { | |
| 2553 trace = trace.substring(4); | |
| 2554 } | |
| 2555 buffer.write('<p>'); | |
| 2556 buffer.write(trace); | |
| 2557 buffer.write('</p>'); | |
| 2558 } | |
| 2559 } | |
| 2560 | |
| 2561 /** | |
| 2562 * Given a [service] that could be subscribed to and a set of the services | |
| 2563 * that are actually subscribed to ([subscribedServices]), write a | |
| 2564 * representation of the service to the given [buffer]. | |
| 2565 */ | |
| 2566 void _writeSubscriptionInList( | |
| 2567 StringBuffer buffer, Enum service, Set<Enum> subscribedServices) { | |
| 2568 if (subscribedServices.contains(service)) { | |
| 2569 buffer.write('<code>+ </code>'); | |
| 2570 } else { | |
| 2571 buffer.write('<code>- </code>'); | |
| 2572 } | |
| 2573 buffer.write(service.name); | |
| 2574 buffer.write('<br>'); | |
| 2575 } | |
| 2576 | |
| 2577 /** | |
| 2578 * Given a [service] that could be subscribed to and a set of paths that are | |
| 2579 * subscribed to the services ([subscribedPaths]), write a representation of | |
| 2580 * the service to the given [buffer]. | |
| 2581 */ | |
| 2582 void _writeSubscriptionInMap( | |
| 2583 StringBuffer buffer, Enum service, Set<String> subscribedPaths) { | |
| 2584 buffer.write('<p>'); | |
| 2585 buffer.write(service.name); | |
| 2586 buffer.write('</p>'); | |
| 2587 if (subscribedPaths == null || subscribedPaths.isEmpty) { | |
| 2588 buffer.write('none'); | |
| 2589 } else { | |
| 2590 List<String> paths = subscribedPaths.toList(); | |
| 2591 paths.sort(); | |
| 2592 for (String path in paths) { | |
| 2593 buffer.write('<p>'); | |
| 2594 buffer.write(path); | |
| 2595 buffer.write('</p>'); | |
| 2596 } | |
| 2597 } | |
| 2598 } | |
| 2599 | |
| 2600 /** | |
| 2601 * Given a list containing all of the services that can be subscribed to in a | |
| 2602 * single domain ([allServices]) and a set of the services that are actually | |
| 2603 * subscribed to ([subscribedServices]), write a representation of the | |
| 2604 * subscriptions to the given [buffer]. | |
| 2605 */ | |
| 2606 void _writeSubscriptionList(StringBuffer buffer, List<Enum> allServices, | |
| 2607 Set<Enum> subscribedServices) { | |
| 2608 buffer.write('<p><b>Subscriptions</b></p>'); | |
| 2609 buffer.write('<p>'); | |
| 2610 for (Enum service in allServices) { | |
| 2611 _writeSubscriptionInList(buffer, service, subscribedServices); | |
| 2612 } | |
| 2613 buffer.write('</p>'); | |
| 2614 } | |
| 2615 | |
| 2616 /** | |
| 2617 * Given a list containing all of the services that can be subscribed to in a | |
| 2618 * single domain ([allServices]) and a set of the services that are actually | |
| 2619 * subscribed to ([subscribedServices]), write a representation of the | |
| 2620 * subscriptions to the given [buffer]. | |
| 2621 */ | |
| 2622 void _writeSubscriptionMap(StringBuffer buffer, List<Enum> allServices, | |
| 2623 Map<Enum, Set<String>> subscribedServices) { | |
| 2624 buffer.write('<p><b>Subscriptions</b></p>'); | |
| 2625 for (Enum service in allServices) { | |
| 2626 _writeSubscriptionInMap(buffer, service, subscribedServices[service]); | |
| 2627 } | |
| 2628 } | |
| 2629 | |
| 2630 /** | |
| 2631 * Write the targeted results returned by iterating over the [results] to the | |
| 2632 * given [buffer]. The list will have the given [title] written before it. | |
| 2633 */ | |
| 2634 void _writeTargetedResults( | |
| 2635 StringBuffer buffer, String title, Iterable<TargetedResult> results) { | |
| 2636 List<TargetedResult> sortedResults = results.toList(); | |
| 2637 sortedResults.sort((TargetedResult first, TargetedResult second) { | |
| 2638 int nameOrder = | |
| 2639 first.result.toString().compareTo(second.result.toString()); | |
| 2640 if (nameOrder != 0) { | |
| 2641 return nameOrder; | |
| 2642 } | |
| 2643 return first.target.toString().compareTo(second.target.toString()); | |
| 2644 }); | |
| 2645 | |
| 2646 buffer.write('<p>'); | |
| 2647 buffer.write(title); | |
| 2648 buffer.write('</p><blockquote>'); | |
| 2649 if (results.isEmpty) { | |
| 2650 buffer.write('nothing'); | |
| 2651 } else { | |
| 2652 for (TargetedResult result in sortedResults) { | |
| 2653 buffer.write('<p>'); | |
| 2654 buffer.write(result.result.toString()); | |
| 2655 buffer.write(' of '); | |
| 2656 buffer.write(result.target.toString()); | |
| 2657 buffer.write('<p>'); | |
| 2658 } | |
| 2659 } | |
| 2660 buffer.write('</blockquote>'); | |
| 2661 } | |
| 2662 | |
| 2663 /** | |
| 2664 * Write two columns of information to the given [buffer], where the | |
| 2665 * [leftColumn] and [rightColumn] functions are used to generate the content | |
| 2666 * of those columns. | |
| 2667 */ | |
| 2668 void _writeTwoColumns(StringBuffer buffer, HtmlGenerator leftColumn, | |
| 2669 HtmlGenerator rightColumn) { | |
| 2670 buffer | |
| 2671 .write('<table class="column"><tr class="column"><td class="column">'); | |
| 2672 leftColumn(buffer); | |
| 2673 buffer.write('</td><td class="column">'); | |
| 2674 rightColumn(buffer); | |
| 2675 buffer.write('</td></tr></table>'); | |
| 2676 } | |
| 2677 | |
| 2678 /** | |
| 2679 * Render the given [value] as HTML and append it to the given [buffer]. The | |
| 2680 * [linkParameters] will be used if the value is too large to be displayed on | |
| 2681 * the current page and needs to be linked to a separate page. | |
| 2682 */ | |
| 2683 void _writeValueAsHtml( | |
| 2684 StringBuffer buffer, Object value, Map<String, String> linkParameters) { | |
| 2685 if (value == null) { | |
| 2686 buffer.write('<i>null</i>'); | |
| 2687 } else if (value is String) { | |
| 2688 buffer.write('<pre>${HTML_ESCAPE.convert(value)}</pre>'); | |
| 2689 } else if (value is List) { | |
| 2690 buffer.write('List containing ${value.length} entries'); | |
| 2691 buffer.write('<ul>'); | |
| 2692 for (var entry in value) { | |
| 2693 _writeListItem(buffer, () { | |
| 2694 _writeValueAsHtml(buffer, entry, linkParameters); | |
| 2695 }); | |
| 2696 } | |
| 2697 buffer.write('</ul>'); | |
| 2698 } else if (value is AstNode) { | |
| 2699 String link = | |
| 2700 makeLink(AST_PATH, linkParameters, value.runtimeType.toString()); | |
| 2701 buffer.write('<i>$link</i>'); | |
| 2702 } else if (value is Element) { | |
| 2703 String link = | |
| 2704 makeLink(ELEMENT_PATH, linkParameters, value.runtimeType.toString()); | |
| 2705 buffer.write('<i>$link</i>'); | |
| 2706 } else if (value is UsedLocalElements) { | |
| 2707 buffer.write('<ul>'); | |
| 2708 _writeListItem(buffer, () { | |
| 2709 HashSet<Element> elements = value.elements; | |
| 2710 buffer.write('List "elements" containing ${elements.length} entries'); | |
| 2711 buffer.write('<ul>'); | |
| 2712 for (Element element in elements) { | |
| 2713 _writeListItem(buffer, () { | |
| 2714 String elementStr = HTML_ESCAPE.convert(element.toString()); | |
| 2715 buffer.write('<i>${element.runtimeType}</i> $elementStr'); | |
| 2716 }); | |
| 2717 } | |
| 2718 buffer.write('</ul>'); | |
| 2719 }); | |
| 2720 _writeListItem(buffer, () { | |
| 2721 _writeListOfStrings(buffer, 'members', value.members); | |
| 2722 }); | |
| 2723 _writeListItem(buffer, () { | |
| 2724 _writeListOfStrings(buffer, 'readMembers', value.readMembers); | |
| 2725 }); | |
| 2726 buffer.write('</ul>'); | |
| 2727 } else { | |
| 2728 buffer.write(HTML_ESCAPE.convert(value.toString())); | |
| 2729 buffer.write(' <i>(${value.runtimeType.toString()})</i>'); | |
| 2730 } | |
| 2731 } | |
| 2732 | |
| 2733 /** | |
| 2734 * Create a link to [path] with query parameters [params], with inner HTML | |
| 2735 * [innerHtml]. If [hasError] is `true`, then the link will have the class | |
| 2736 * 'error'. | |
| 2737 */ | |
| 2738 static String makeLink( | |
| 2739 String path, Map<String, String> params, String innerHtml, | |
| 2740 [bool hasError = false]) { | |
| 2741 Uri uri = new Uri(path: path, queryParameters: params); | |
| 2742 String href = HTML_ESCAPE.convert(uri.toString()); | |
| 2743 String classAttribute = hasError ? ' class="error"' : ''; | |
| 2744 return '<a href="$href"$classAttribute>$innerHtml</a>'; | |
| 2745 } | |
| 2746 } | |
| OLD | NEW |