// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. library analysis_server.src.get_handler; import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:analysis_server/src/analysis_server.dart'; import 'package:analysis_server/src/domain_completion.dart'; import 'package:analysis_server/src/domain_execution.dart'; import 'package:analysis_server/src/operation/operation.dart'; import 'package:analysis_server/src/operation/operation_analysis.dart'; import 'package:analysis_server/src/operation/operation_queue.dart'; import 'package:analysis_server/src/protocol.dart' hide Element; import 'package:analysis_server/src/services/index/index.dart'; import 'package:analysis_server/src/services/index/local_index.dart'; import 'package:analysis_server/src/services/index/store/split_store.dart'; import 'package:analysis_server/src/socket_server.dart'; import 'package:analysis_server/src/status/ast_writer.dart'; import 'package:analysis_server/src/status/element_writer.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/element.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/generated/java_engine.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/generated/utilities_general.dart'; import 'package:analyzer/task/model.dart' as newTask; import 'package:plugin/plugin.dart'; /** * A function that can be used to generate HTML output into the given [buffer]. * The HTML that is generated must be valid (special characters must already be * encoded). */ typedef void HtmlGenerator(StringBuffer buffer); /** * Instances of the class [GetHandler] handle GET requests. */ class GetHandler { /** * The path used to request overall performance information. */ static const String ANALYSIS_PERFORMANCE_PATH = '/perf/analysis'; /** * The path used to request information about a element model. */ static const String AST_PATH = '/ast'; /** * The path used to request information about the cache entry corresponding * to a single file. */ static const String CACHE_ENTRY_PATH = '/cache_entry'; /** * The path used to request the list of source files in a certain cache * state. */ static const String CACHE_STATE_PATH = '/cache_state'; /** * The path used to request code completion information. */ static const String COMPLETION_PATH = '/completion'; /** * The path used to request communication performance information. */ static const String COMMUNICATION_PERFORMANCE_PATH = '/perf/communication'; /** * The path used to request information about a specific context. */ static const String CONTEXT_PATH = '/context'; /** * The path used to request information about a element model. */ static const String ELEMENT_PATH = '/element'; /** * The path used to request information about elements with the given name. */ static const String INDEX_ELEMENT_BY_NAME = '/index/element-by-name'; /** * The path used to request an overlay contents. */ static const String OVERLAY_PATH = '/overlay'; /** * The path used to request overlays information. */ static const String OVERLAYS_PATH = '/overlays'; /** * The path used to request the status of the analysis server as a whole. */ static const String STATUS_PATH = '/status'; /** * Query parameter used to represent the context to search for, when * accessing [CACHE_ENTRY_PATH] or [CACHE_STATE_PATH]. */ static const String CONTEXT_QUERY_PARAM = 'context'; /** * Query parameter used to represent the descriptor to search for, when * accessing [CACHE_STATE_PATH]. */ static const String DESCRIPTOR_QUERY_PARAM = 'descriptor'; /** * Query parameter used to represent the name of elements to search for, when * accessing [INDEX_ELEMENT_BY_NAME]. */ static const String INDEX_ELEMENT_NAME = 'name'; /** * Query parameter used to represent the path of an overlayed file. */ static const String PATH_PARAM = 'path'; /** * Query parameter used to represent the source to search for, when accessing * [CACHE_ENTRY_PATH]. */ static const String SOURCE_QUERY_PARAM = 'entry'; /** * Query parameter used to represent the cache state to search for, when * accessing [CACHE_STATE_PATH]. */ static const String STATE_QUERY_PARAM = 'state'; static final ContentType _htmlContent = new ContentType("text", "html", charset: "utf-8"); /** * The socket server whose status is to be reported on. */ SocketServer _server; /** * Buffer containing strings printed by the analysis server. */ List _printBuffer; /** * Contents of overlay files. */ final Map _overlayContents = {}; /** * Initialize a newly created handler for GET requests. */ GetHandler(this._server, this._printBuffer); /** * Return the active [CompletionDomainHandler] * or `null` if either analysis server is not running * or there is no completion domain handler. */ CompletionDomainHandler get _completionDomainHandler { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return null; } return analysisServer.handlers.firstWhere( (h) => h is CompletionDomainHandler, orElse: () => null); } /** * Handle a GET request received by the HTTP server. */ void handleGetRequest(HttpRequest request) { String path = request.uri.path; if (path == STATUS_PATH) { _returnServerStatus(request); } else if (path == ANALYSIS_PERFORMANCE_PATH) { _returnAnalysisPerformance(request); } else if (path == AST_PATH) { _returnAst(request); } else if (path == CACHE_STATE_PATH) { _returnCacheState(request); } else if (path == CACHE_ENTRY_PATH) { _returnCacheEntry(request); } else if (path == COMPLETION_PATH) { _returnCompletionInfo(request); } else if (path == COMMUNICATION_PERFORMANCE_PATH) { _returnCommunicationPerformance(request); } else if (path == CONTEXT_PATH) { _returnContextInfo(request); } else if (path == ELEMENT_PATH) { _returnElement(request); } else if (path == INDEX_ELEMENT_BY_NAME) { _returnIndexElementByName(request); } else if (path == OVERLAY_PATH) { _returnOverlayContents(request); } else if (path == OVERLAYS_PATH) { _returnOverlaysInfo(request); } else { _returnUnknownRequest(request); } } /** * Return the folder being managed by the given [analysisServer] that matches * the given [contextFilter], or `null` if there is none. */ Folder _findFolder(AnalysisServer analysisServer, String contextFilter) { return analysisServer.folderMap.keys.firstWhere( (Folder folder) => folder.path == contextFilter, orElse: () => null); } /** * Return `true` if the given analysis [context] has at least one entry with * an exception. */ bool _hasException(AnalysisContextImpl context) { bool hasException = false; context.visitCacheItems((Source source, SourceEntry sourceEntry, DataDescriptor rowDesc, CacheState state) { if (sourceEntry.exception != null) { hasException = true; } }); return hasException; } /** * Return the folder in the [folderMap] with which the given [context] is * associated. */ Folder _keyForValue( Map folderMap, AnalysisContext context) { for (Folder folder in folderMap.keys) { if (folderMap[folder] == context) { return folder; } } return null; } /** * Return a response displaying overall performance information. */ void _returnAnalysisPerformance(HttpRequest request) { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server is not running'); } _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Analysis Performance', [], (StringBuffer buffer) { buffer.write('

Analysis Performance

'); // // Write performance tags. // { buffer.write('

Time spent in each phase of analysis

'); buffer.write( ''); _writeRow(buffer, ['Time (in ms)', 'Percent', 'Analysis Phase'], header: true); // prepare sorted tags List tags = PerformanceTag.all.toList(); tags.remove(ServerPerformanceStatistics.idle); tags.sort((a, b) => b.elapsedMs - a.elapsedMs); // prepare total time int totalTime = 0; tags.forEach((PerformanceTag tag) { totalTime += tag.elapsedMs; }); // write rows void writeRow(PerformanceTag tag) { double percent = (tag.elapsedMs * 100) / totalTime; String percentStr = '${percent.toStringAsFixed(2)}%'; _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label], classes: ["right", "right", null]); } tags.forEach(writeRow); buffer.write('
'); } // // Write new task model timing information. // if (AnalysisEngine.instance.useTaskModel) { buffer.write('

Task performace data

'); buffer.write( ''); _writeRow(buffer, [ 'Task Name', 'Count', 'Total Time (in ms)', 'Average Time (in ms)' ], header: true); Map countMap = newTask.AnalysisTask.countMap; Map stopwatchMap = newTask.AnalysisTask.stopwatchMap; List taskClasses = stopwatchMap.keys.toList(); taskClasses.sort((Type first, Type second) => first.toString().compareTo(second.toString())); int totalTime = 0; taskClasses.forEach((Type taskClass) { int count = countMap[taskClass]; if (count == null) { count = 0; } int taskTime = stopwatchMap[taskClass].elapsedMilliseconds; totalTime += taskTime; _writeRow(buffer, [ taskClass.toString(), count, taskTime, count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3) ], classes: [null, "right", "right", "right"]); }); _writeRow(buffer, ['Total', '-', totalTime, '-'], classes: [null, "right", "right", "right"]); buffer.write('
'); } // // Write old task model transition information. // { Map> transitionMap = SourceEntry.transitionMap; buffer.write( '

Number of times a state transitioned to VALID (grouped by descriptor)

'); if (transitionMap.isEmpty) { buffer.write('

none

'); } else { List descriptors = transitionMap.keys.toList(); descriptors.sort((DataDescriptor first, DataDescriptor second) => first.toString().compareTo(second.toString())); for (DataDescriptor key in descriptors) { Map countMap = transitionMap[key]; List oldStates = countMap.keys.toList(); oldStates.sort((CacheState first, CacheState second) => first.toString().compareTo(second.toString())); buffer.write('

${key.toString()}

'); buffer.write( ''); _writeRow(buffer, ['Count', 'Previous State'], header: true); for (CacheState state in oldStates) { _writeRow(buffer, [countMap[state], state.toString()], classes: ["right", null]); } buffer.write('
'); } } } }); }); } /** * Return a response containing information about an AST structure. */ void _returnAst(HttpRequest request) { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server not running'); } String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; if (contextFilter == null) { return _returnFailure( request, 'Query parameter $CONTEXT_QUERY_PARAM required'); } Folder folder = _findFolder(analysisServer, contextFilter); if (folder == null) { return _returnFailure(request, 'Invalid context: $contextFilter'); } String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; if (sourceUri == null) { return _returnFailure( request, 'Query parameter $SOURCE_QUERY_PARAM required'); } AnalysisContextImpl context = analysisServer.folderMap[folder]; _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - AST Structure', [ 'Context: $contextFilter', 'File: $sourceUri' ], (HttpResponse) { Source source = context.sourceFactory.forUri(sourceUri); if (source == null) { buffer.write('

Not found.

'); return; } SourceEntry entry = context.getReadableSourceEntryOrNull(source); if (entry == null) { buffer.write('

Not found.

'); return; } CompilationUnit ast = (entry as DartEntry).anyParsedCompilationUnit; if (ast == null) { buffer.write('

null

'); return; } AstWriter writer = new AstWriter(buffer); ast.accept(writer); if (writer.exceptions.isNotEmpty) { buffer.write('

Exceptions while creating page

'); for (CaughtException exception in writer.exceptions) { _writeException(buffer, exception); } } }); }); } /** * Return a response containing information about a single source file in the * cache. */ void _returnCacheEntry(HttpRequest request) { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server not running'); } String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; if (contextFilter == null) { return _returnFailure( request, 'Query parameter $CONTEXT_QUERY_PARAM required'); } Folder folder = _findFolder(analysisServer, contextFilter); if (folder == null) { return _returnFailure(request, 'Invalid context: $contextFilter'); } String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; if (sourceUri == null) { return _returnFailure( request, 'Query parameter $SOURCE_QUERY_PARAM required'); } List allContexts = []; Map entryMap = new HashMap(); analysisServer.folderMap .forEach((Folder folder, AnalysisContextImpl context) { Source source = context.sourceFactory.forUri(sourceUri); if (source != null) { SourceEntry entry = context.getReadableSourceEntryOrNull(source); if (entry != null) { allContexts.add(folder); entryMap[folder] = entry; } } }); allContexts.sort((Folder firstFolder, Folder secondFolder) => firstFolder.path.compareTo(secondFolder.path)); AnalysisContextImpl context = analysisServer.folderMap[folder]; _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Cache Entry', [ 'Context: $contextFilter', 'File: $sourceUri' ], (HttpResponse) { buffer.write('

Analyzing Contexts

'); bool first = true; allContexts.forEach((Folder folder) { if (first) { first = false; } else { buffer.write('
'); } AnalysisContextImpl analyzingContext = analysisServer.folderMap[folder]; if (analyzingContext == context) { buffer.write(folder.path); } else { buffer.write(makeLink(CACHE_ENTRY_PATH, { CONTEXT_QUERY_PARAM: folder.path, SOURCE_QUERY_PARAM: sourceUri }, HTML_ESCAPE.convert(folder.path))); } if (entryMap[folder].explicitlyAdded) { buffer.write(' (explicit)'); } else { buffer.write(' (implicit)'); } }); buffer.write('

'); SourceEntry entry = entryMap[folder]; if (entry == null) { buffer.write('

Not being analyzed in this context.

'); return; } Map linkParameters = { CONTEXT_QUERY_PARAM: folder.path, SOURCE_QUERY_PARAM: sourceUri }; buffer.write('

Library Independent

'); _writeDescriptorTable(buffer, entry.descriptors, entry.getState, entry.getValue, linkParameters); if (entry is DartEntry) { for (Source librarySource in entry.containingLibraries) { String libraryName = HTML_ESCAPE.convert(librarySource.fullName); buffer.write('

In library $libraryName:

'); _writeDescriptorTable(buffer, entry.libraryDescriptors, (DataDescriptor descriptor) => entry.getStateInLibrary(descriptor, librarySource), (DataDescriptor descriptor) => entry.getValueInLibrary(descriptor, librarySource), linkParameters); } } if (entry.exception != null) { buffer.write('

Exception

'); _writeException(buffer, entry.exception); } }); }); } /** * Return a response indicating the set of source files in a certain cache * state. */ void _returnCacheState(HttpRequest request) { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server not running'); } // Figure out which context is being searched within. String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; if (contextFilter == null) { return _returnFailure( request, 'Query parameter $CONTEXT_QUERY_PARAM required'); } // Figure out what CacheState is being searched for. String stateQueryParam = request.uri.queryParameters[STATE_QUERY_PARAM]; if (stateQueryParam == null) { return _returnFailure( request, 'Query parameter $STATE_QUERY_PARAM required'); } CacheState stateFilter = null; for (CacheState value in CacheState.values) { if (value.toString() == stateQueryParam) { stateFilter = value; } } if (stateFilter == null) { return _returnFailure( request, 'Query parameter $STATE_QUERY_PARAM is invalid'); } // Figure out which descriptor is being searched for. String descriptorFilter = request.uri.queryParameters[DESCRIPTOR_QUERY_PARAM]; if (descriptorFilter == null) { return _returnFailure( request, 'Query parameter $DESCRIPTOR_QUERY_PARAM required'); } Folder folder = _findFolder(analysisServer, contextFilter); AnalysisContextImpl context = analysisServer.folderMap[folder]; List links = []; context.visitCacheItems((Source source, SourceEntry dartEntry, DataDescriptor rowDesc, CacheState state) { if (state != stateFilter || rowDesc.toString() != descriptorFilter) { return; } String link = makeLink(CACHE_ENTRY_PATH, { CONTEXT_QUERY_PARAM: folder.path, SOURCE_QUERY_PARAM: source.uri.toString() }, HTML_ESCAPE.convert(source.fullName)); links.add(link); }); _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Cache Search', [ 'Context: $contextFilter', 'Descriptor: ${HTML_ESCAPE.convert(descriptorFilter)}', 'State: ${HTML_ESCAPE.convert(stateQueryParam)}' ], (StringBuffer buffer) { buffer.write('

${links.length} files found

'); buffer.write('
    '); links.forEach((String link) { buffer.write('
  • $link
  • '); }); buffer.write('
'); }); }); } /** * Return a response displaying overall performance information. */ void _returnCommunicationPerformance(HttpRequest request) { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server is not running'); } _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Communication Performance', [], (StringBuffer buffer) { buffer.write('

Communication Performance

'); _writeTwoColumns(buffer, (StringBuffer buffer) { ServerPerformance perf = analysisServer.performanceDuringStartup; int requestCount = perf.requestCount; num averageLatency = requestCount > 0 ? (perf.requestLatency / requestCount).round() : 0; int maximumLatency = perf.maxLatency; num slowRequestPercent = requestCount > 0 ? (perf.slowRequestCount * 100 / requestCount).round() : 0; buffer.write('

Startup

'); buffer.write(''); _writeRow(buffer, [requestCount, 'requests'], classes: ["right", null]); _writeRow(buffer, [averageLatency, 'ms average latency'], classes: ["right", null]); _writeRow(buffer, [maximumLatency, 'ms maximum latency'], classes: ["right", null]); _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], classes: ["right", null]); if (analysisServer.performanceAfterStartup != null) { int startupTime = analysisServer.performanceAfterStartup.startTime - perf.startTime; _writeRow( buffer, [startupTime, 'ms for initial analysis to complete']); } buffer.write('
'); }, (StringBuffer buffer) { ServerPerformance perf = analysisServer.performanceAfterStartup; if (perf == null) { return; } int requestCount = perf.requestCount; num averageLatency = requestCount > 0 ? (perf.requestLatency * 10 / requestCount).round() / 10 : 0; int maximumLatency = perf.maxLatency; num slowRequestPercent = requestCount > 0 ? (perf.slowRequestCount * 100 / requestCount).round() : 0; buffer.write('

Current

'); buffer.write(''); _writeRow(buffer, [requestCount, 'requests'], classes: ["right", null]); _writeRow(buffer, [averageLatency, 'ms average latency'], classes: ["right", null]); _writeRow(buffer, [maximumLatency, 'ms maximum latency'], classes: ["right", null]); _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], classes: ["right", null]); buffer.write('
'); }); }); }); } /** * Return a response displaying code completion information. */ void _returnCompletionInfo(HttpRequest request) { String value = request.requestedUri.queryParameters['index']; int index = value != null ? int.parse(value, onError: (_) => 0) : 0; _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Completion Stats', [], (StringBuffer buffer) { _writeCompletionPerformanceDetail(buffer, index); _writeCompletionPerformanceList(buffer); }); }); } /** * Return a response containing information about a single source file in the * cache. */ void _returnContextInfo(HttpRequest request) { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server not running'); } String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; if (contextFilter == null) { return _returnFailure( request, 'Query parameter $CONTEXT_QUERY_PARAM required'); } Folder folder = _findFolder(analysisServer, contextFilter); if (folder == null) { return _returnFailure(request, 'Invalid context: $contextFilter'); } List priorityNames; List explicitNames = []; List implicitNames = []; Map links = new HashMap(); List exceptions = []; AnalysisContextImpl context = analysisServer.folderMap[folder]; priorityNames = context.prioritySources .map((Source source) => source.fullName) .toList(); context.visitCacheItems((Source source, SourceEntry sourceEntry, DataDescriptor rowDesc, CacheState state) { String sourceName = source.fullName; if (!links.containsKey(sourceName)) { CaughtException exception = sourceEntry.exception; if (exception != null) { exceptions.add(exception); } String link = makeLink(CACHE_ENTRY_PATH, { CONTEXT_QUERY_PARAM: folder.path, SOURCE_QUERY_PARAM: source.uri.toString() }, sourceName, exception != null); if (sourceEntry.explicitlyAdded) { explicitNames.add(sourceName); } else { implicitNames.add(sourceName); } links[sourceName] = link; } }); explicitNames.sort(); implicitNames.sort(); _overlayContents.clear(); context.visitContentCache((String fullName, int stamp, String contents) { _overlayContents[fullName] = contents; }); void _writeFiles( StringBuffer buffer, String title, List fileNames) { buffer.write('

$title

'); if (fileNames == null || fileNames.isEmpty) { buffer.write('

None

'); } else { buffer.write(''); for (String fileName in fileNames) { buffer.write(''); } buffer.write('
'); buffer.write(links[fileName]); buffer.write(''); if (_overlayContents.containsKey(fileName)) { buffer.write( makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay')); } buffer.write('
'); } } _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Context', ['Context: $contextFilter'], (StringBuffer buffer) { List headerRowText = ['Context']; headerRowText.addAll(CacheState.values); buffer.write('

Summary

'); buffer.write(''); _writeRow(buffer, headerRowText, header: true); AnalysisContextStatistics statistics = context.statistics; statistics.cacheRows.forEach((AnalysisContextStatistics_CacheRow row) { List rowText = [row.name]; for (CacheState state in CacheState.values) { String text = row.getCount(state).toString(); Map params = { STATE_QUERY_PARAM: state.toString(), CONTEXT_QUERY_PARAM: folder.path, DESCRIPTOR_QUERY_PARAM: row.name }; rowText.add(makeLink(CACHE_STATE_PATH, params, text)); } _writeRow(buffer, rowText, classes: [null, "right"]); }); buffer.write('
'); _writeFiles(buffer, 'Priority Files', priorityNames); _writeFiles(buffer, 'Explicitly Analyzed Files', explicitNames); _writeFiles(buffer, 'Implicitly Analyzed Files', implicitNames); buffer.write('

Exceptions

'); if (exceptions.isEmpty) { buffer.write('

None

'); } else { exceptions.forEach((CaughtException exception) { _writeException(buffer, exception); }); } }); }); } /** * Return a response containing information about an element structure. */ void _returnElement(HttpRequest request) { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server not running'); } String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; if (contextFilter == null) { return _returnFailure( request, 'Query parameter $CONTEXT_QUERY_PARAM required'); } Folder folder = _findFolder(analysisServer, contextFilter); if (folder == null) { return _returnFailure(request, 'Invalid context: $contextFilter'); } String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; if (sourceUri == null) { return _returnFailure( request, 'Query parameter $SOURCE_QUERY_PARAM required'); } AnalysisContextImpl context = analysisServer.folderMap[folder]; _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Element Model', [ 'Context: $contextFilter', 'File: $sourceUri' ], (StringBuffer buffer) { Source source = context.sourceFactory.forUri(sourceUri); if (source == null) { buffer.write('

Not found.

'); return; } SourceEntry entry = context.getReadableSourceEntryOrNull(source); if (entry == null) { buffer.write('

Not found.

'); return; } LibraryElement element = entry.getValue(DartEntry.ELEMENT); if (element == null) { buffer.write('

null

'); return; } element.accept(new ElementWriter(buffer)); }); }); } void _returnFailure(HttpRequest request, String message) { _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Failure', [], (StringBuffer buffer) { buffer.write(HTML_ESCAPE.convert(message)); }); }); } /** * Return a response containing information about elements with the given * name. */ Future _returnIndexElementByName(HttpRequest request) async { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server not running'); } Index index = analysisServer.index; if (index == null) { return _returnFailure(request, 'Indexing is disabled'); } String name = request.uri.queryParameters[INDEX_ELEMENT_NAME]; if (name == null) { return _returnFailure( request, 'Query parameter $INDEX_ELEMENT_NAME required'); } if (index is LocalIndex) { Map, List> relations = await index.findElementsByName(name); _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Index Elements', ['Name: $name'], (StringBuffer buffer) { buffer.write(''); _writeRow(buffer, ['Element', 'Relationship', 'Location'], header: true); relations.forEach((List elementPath, List relations) { String elementLocation = elementPath.join(' '); relations.forEach((InspectLocation location) { var relString = location.relationship.identifier; var locString = '${location.path} offset=${location.offset} ' 'length=${location.length} flags=${location.flags}'; _writeRow(buffer, [elementLocation, relString, locString]); }); }); buffer.write('
'); }); }); } else { return _returnFailure(request, 'LocalIndex expected, but $index found.'); } } void _returnOverlayContents(HttpRequest request) { String path = request.requestedUri.queryParameters[PATH_PARAM]; if (path == null) { return _returnFailure(request, 'Query parameter $PATH_PARAM required'); } String contents = _overlayContents[path]; _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Overlay', [], (StringBuffer buffer) { buffer.write('
${HTML_ESCAPE.convert(contents)}
'); }); }); } /** * Return a response displaying overlays information. */ void _returnOverlaysInfo(HttpRequest request) { AnalysisServer analysisServer = _server.analysisServer; if (analysisServer == null) { return _returnFailure(request, 'Analysis server is not running'); } _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Overlays', [], (StringBuffer buffer) { buffer.write(''); _overlayContents.clear(); ContentCache overlayState = analysisServer.overlayState; overlayState.accept((String fullName, int stamp, String contents) { buffer.write(''); String link = makeLink(OVERLAY_PATH, {PATH_PARAM: fullName}, fullName); DateTime time = new DateTime.fromMillisecondsSinceEpoch(stamp); _writeRow(buffer, [link, time]); _overlayContents[fullName] = contents; }); int count = _overlayContents.length; buffer.write(''); buffer.write('
Total: $count entries.
'); }); }); } /** * Return a response indicating the status of the analysis server. */ void _returnServerStatus(HttpRequest request) { _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server - Status', [], (StringBuffer buffer) { if (_writeServerStatus(buffer)) { _writeAnalysisStatus(buffer); _writeEditStatus(buffer); _writeExecutionStatus(buffer); _writePluginStatus(buffer); _writeRecentOutput(buffer); } }); }); } /** * Return an error in response to an unrecognized request received by the HTTP * server. */ void _returnUnknownRequest(HttpRequest request) { _writeResponse(request, (StringBuffer buffer) { _writePage(buffer, 'Analysis Server', [], (StringBuffer buffer) { buffer.write('

Pages

'); buffer.write('

'); buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data')); buffer.write('

'); buffer.write('

'); buffer .write(makeLink(COMMUNICATION_PERFORMANCE_PATH, {}, 'Performance')); buffer.write('

'); buffer.write('

'); buffer.write(makeLink(STATUS_PATH, {}, 'Server status')); buffer.write('

'); buffer.write('

'); buffer.write(makeLink(OVERLAYS_PATH, {}, 'File overlays')); buffer.write('

'); }); }); } /** * Return a two digit decimal representation of the given non-negative integer * [value]. */ String _twoDigit(int value) { if (value < 10) { return '0$value'; } return value.toString(); } /** * Write the status of the analysis domain (on the main status page) to the * given [buffer] object. */ void _writeAnalysisStatus(StringBuffer buffer) { AnalysisServer analysisServer = _server.analysisServer; Map folderMap = analysisServer.folderMap; List folders = folderMap.keys.toList(); folders.sort((Folder first, Folder second) => first.shortName.compareTo(second.shortName)); AnalysisOptionsImpl options = analysisServer.contextDirectoryManager.defaultOptions; ServerOperationQueue operationQueue = analysisServer.operationQueue; buffer.write('

Analysis Domain

'); _writeTwoColumns(buffer, (StringBuffer buffer) { if (operationQueue.isEmpty) { buffer.write('

Status: Done analyzing

'); } else { ServerOperation operation = operationQueue.peek(); if (operation is PerformAnalysisOperation) { Folder folder = _keyForValue(folderMap, operation.context); if (folder == null) { buffer.write('

Status: Analyzing in unmapped context

'); } else { buffer.write('

Status: Analyzing in ${folder.path}

'); } } else { buffer.write('

Status: Analyzing

'); } } buffer.write('

Analysis Contexts

'); buffer.write('

'); bool first = true; folders.forEach((Folder folder) { if (first) { first = false; } else { buffer.write('
'); } String key = folder.shortName; buffer.write(makeLink(CONTEXT_PATH, { CONTEXT_QUERY_PARAM: folder.path }, key, _hasException(folderMap[folder]))); }); buffer.write('

'); buffer.write('

Options

'); buffer.write('

'); _writeOption( buffer, 'Analyze functon bodies', options.analyzeFunctionBodies); _writeOption(buffer, 'Cache size', options.cacheSize); _writeOption(buffer, 'Enable null-aware operators', options.enableNullAwareOperators); _writeOption( buffer, 'Enable strict call checks', options.enableStrictCallChecks); _writeOption(buffer, 'Generate hints', options.hint); _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint); _writeOption(buffer, 'Generate errors in implicit files', options.generateImplicitErrors); _writeOption( buffer, 'Generate errors in SDK files', options.generateSdkErrors); _writeOption(buffer, 'Incremental resolution', options.incremental); _writeOption(buffer, 'Incremental resolution with API changes', options.incrementalApi); _writeOption(buffer, 'Preserve comments', options.preserveComments, last: true); buffer.write('

'); int freq = AnalysisServer.performOperationDelayFreqency; String delay = freq > 0 ? '1 ms every $freq ms' : 'off'; buffer.write('

perform operation delay: $delay

'); buffer.write('

Performance Data

'); buffer.write('

'); buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data')); buffer.write('

'); }, (StringBuffer buffer) { _writeSubscriptionMap( buffer, AnalysisService.VALUES, analysisServer.analysisServices); }); } /** * Write performance information about a specific completion request * to the given [buffer] object. */ void _writeCompletionPerformanceDetail(StringBuffer buffer, int index) { CompletionDomainHandler handler = _completionDomainHandler; CompletionPerformance performance; if (handler != null) { List list = handler.performanceList; if (list != null && list.isNotEmpty) { performance = list[max(0, min(list.length - 1, index))]; } } if (performance == null) { buffer.write('

Completion Performance Detail

'); buffer.write('

No completions yet

'); return; } buffer.write('

Completion Performance Detail

'); buffer.write('

${performance.startTimeAndMs} for ${performance.source}'); buffer.write(''); _writeRow(buffer, ['Elapsed', '', 'Operation'], header: true); performance.operations.forEach((OperationPerformance op) { String elapsed = op.elapsed != null ? op.elapsed.toString() : '???'; _writeRow(buffer, [elapsed, '  ', op.name]); }); buffer.write('
'); buffer.write('

Compute Cache Performance: '); if (handler.computeCachePerformance == null) { buffer.write('none'); } else { int elapsed = handler.computeCachePerformance.elapsedInMilliseconds; Source source = handler.computeCachePerformance.source; buffer.write(' $elapsed ms for $source'); } buffer.write('

'); } /** * Write a table showing summary information for the last several * completion requests to the given [buffer] object. */ void _writeCompletionPerformanceList(StringBuffer buffer) { CompletionDomainHandler handler = _completionDomainHandler; buffer.write('

Completion Performance List

'); if (handler == null) { return; } buffer.write(''); _writeRow(buffer, [ 'Start Time', '', 'First (ms)', '', 'Complete (ms)', '', '# Notifications', '', '# Suggestions', '', 'Snippet' ], header: true); int index = 0; for (CompletionPerformance performance in handler.performanceList) { String link = makeLink(COMPLETION_PATH, { 'index': '$index' }, '${performance.startTimeAndMs}'); _writeRow(buffer, [ link, '  ', performance.firstNotificationInMilliseconds, '  ', performance.elapsedInMilliseconds, '  ', performance.notificationCount, '  ', performance.suggestionCount, '  ', HTML_ESCAPE.convert(performance.snippet) ]); ++index; } buffer.write('
'); buffer.write('''

First (ms) - the number of milliseconds from when completion received the request until the first notification with completion results was queued for sending back to the client.

Complete (ms) - the number of milliseconds from when completion received the request until the final notification with completion results was queued for sending back to the client.

# Notifications - the total number of notifications sent to the client with completion results for this request.

# Suggestions - the number of suggestions sent to the client in the first notification, followed by a comma, followed by the number of suggestions send to the client in the last notification. If there is only one notification, then there will be only one number in this column.'''); } /** * Generate a table showing the cache values corresponding to the given * [descriptors], using [getState] to get the cache state corresponding to * each descriptor, and [getValue] to get the cached value corresponding to * each descriptor. Append the resulting HTML to the given [buffer]. The * [linkParameters] will be used if the value is too large to be displayed on * the current page and needs to be linked to a separate page. */ void _writeDescriptorTable(StringBuffer buffer, List descriptors, CacheState getState(DataDescriptor), dynamic getValue(DataDescriptor), Map linkParameters) { buffer.write('

'); for (DataDescriptor descriptor in descriptors) { String descriptorName = HTML_ESCAPE.convert(descriptor.toString()); String descriptorState = HTML_ESCAPE.convert(getState(descriptor).toString()); buffer.write('
$descriptorName ($descriptorState)
'); try { _writeValueAsHtml(buffer, getValue(descriptor), linkParameters); } catch (exception) { buffer.write('(${HTML_ESCAPE.convert(exception.toString())})'); } buffer.write('
'); } buffer.write('
'); } /** * Write the status of the edit domain (on the main status page) to the given * [buffer]. */ void _writeEditStatus(StringBuffer buffer) { buffer.write('

Edit Domain

'); _writeTwoColumns(buffer, (StringBuffer buffer) { buffer.write('

Performance Data

'); buffer.write('

'); buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data')); buffer.write('

'); }, (StringBuffer buffer) {}); } /** * Write a representation of the given [caughtException] to the given * [buffer]. If [isCause] is `true`, then the exception was a cause for * another exception. */ void _writeException(StringBuffer buffer, CaughtException caughtException, {bool isCause: false}) { Object exception = caughtException.exception; if (exception is AnalysisException) { buffer.write('

'); if (isCause) { buffer.write('Caused by '); } buffer.write(exception.message); buffer.write('

'); _writeStackTrace(buffer, caughtException.stackTrace); CaughtException cause = exception.cause; if (cause != null) { buffer.write('
'); _writeException(buffer, cause, isCause: true); buffer.write('
'); } } else { buffer.write('

'); if (isCause) { buffer.write('Caused by '); } buffer.write(exception.toString()); buffer.write('

'); _writeStackTrace(buffer, caughtException.stackTrace); } } /** * Write the status of the execution domain (on the main status page) to the * given [buffer]. */ void _writeExecutionStatus(StringBuffer buffer) { AnalysisServer analysisServer = _server.analysisServer; ExecutionDomainHandler handler = analysisServer.handlers.firstWhere( (RequestHandler handler) => handler is ExecutionDomainHandler, orElse: () => null); Set services = new Set(); if (handler.onFileAnalyzed != null) { services.add(ExecutionService.LAUNCH_DATA); } if (handler != null) { buffer.write('

Execution Domain

'); _writeTwoColumns(buffer, (StringBuffer buffer) { _writeSubscriptionList(buffer, ExecutionService.VALUES, services); }, (StringBuffer buffer) {}); } } /** * Write a representation of an analysis option with the given [name] and * [value] to the given [buffer]. The option should be separated from other * options unless the [last] flag is true, indicating that this is the last * option in the list of options. */ void _writeOption(StringBuffer buffer, String name, Object value, {bool last: false}) { buffer.write(name); buffer.write(' = '); buffer.write(value.toString()); if (!last) { buffer.write('
'); } } /** * Write a standard HTML page to the given [buffer]. The page will have the * given [title] and a body that is generated by the given [body] generator. */ void _writePage(StringBuffer buffer, String title, List subtitles, HtmlGenerator body) { DateTime now = new DateTime.now(); String date = "${now.month}/${now.day}/${now.year}"; String time = "${now.hour}:${_twoDigit(now.minute)}:${_twoDigit(now.second)}.${now.millisecond}"; buffer.write(''); buffer.write(''); buffer.write(''); buffer.write(''); buffer.write( ''); buffer.write('$title'); buffer.write(''); buffer.write(''); buffer.write(''); buffer.write( '

$title (as of $time on $date)

'); if (subtitles != null && subtitles.isNotEmpty) { buffer.write('
'); bool first = true; for (String subtitle in subtitles) { if (first) { first = false; } else { buffer.write('
'); } buffer.write(''); buffer.write(subtitle); buffer.write(''); } buffer.write('
'); } try { body(buffer); } catch (exception, stackTrace) { buffer.write('

Exception while creating page

'); _writeException(buffer, new CaughtException(exception, stackTrace)); } buffer.write(''); buffer.write(''); } /** * Write the recent output section (on the main status page) to the given * [buffer] object. */ void _writePluginStatus(StringBuffer buffer) { void writePlugin(Plugin plugin) { buffer.write(_server.serverPlugin.uniqueIdentifier); buffer.write(' ('); buffer.write(_server.serverPlugin.runtimeType); buffer.write(')
'); } buffer.write('

Plugin Status

'); writePlugin(AnalysisEngine.instance.enginePlugin); writePlugin(_server.serverPlugin); for (Plugin plugin in _server.analysisServer.userDefinedPlugins) { writePlugin(plugin); } buffer.write('

'); } /** * Write the recent output section (on the main status page) to the given * [buffer] object. */ void _writeRecentOutput(StringBuffer buffer) { buffer.write('

Recent Output

'); String output = HTML_ESCAPE.convert(_printBuffer.join('\n')); if (output.isEmpty) { buffer.write('none'); } else { buffer.write('
');
      buffer.write(output);
      buffer.write('
'); } } void _writeResponse(HttpRequest request, HtmlGenerator writePage) { HttpResponse response = request.response; response.statusCode = HttpStatus.OK; response.headers.contentType = _htmlContent; try { StringBuffer buffer = new StringBuffer(); try { writePage(buffer); } catch (exception, stackTrace) { buffer.clear(); _writePage(buffer, 'Internal Exception', [], (StringBuffer buffer) { _writeException(buffer, new CaughtException(exception, stackTrace)); }); } response.write(buffer.toString()); } finally { response.close(); } } /** * Write a single row within a table to the given [buffer]. The row will have * one cell for each of the [columns], and will be a header row if [header] is * `true`. */ void _writeRow(StringBuffer buffer, List columns, {bool header: false, List classes}) { buffer.write(''); int count = columns.length; int maxClassIndex = classes == null ? 0 : classes.length - 1; for (int i = 0; i < count; i++) { String classAttribute = ''; if (classes != null) { String className = classes[min(i, maxClassIndex)]; if (className != null) { classAttribute = ' class="$className"'; } } if (header) { buffer.write(''); } else { buffer.write(''); } buffer.write(columns[i]); if (header) { buffer.write(''); } else { buffer.write(''); } } buffer.write(''); } /** * Write the status of the service domain (on the main status page) to the * given [response] object. */ bool _writeServerStatus(StringBuffer buffer) { AnalysisServer analysisServer = _server.analysisServer; Set services = analysisServer.serverServices; buffer.write('

Server Domain

'); _writeTwoColumns(buffer, (StringBuffer buffer) { if (analysisServer == null) { buffer.write('Status: Not running'); return false; } buffer.write('

'); buffer.write('Status: Running
'); buffer.write('Instrumentation: '); if (AnalysisEngine.instance.instrumentationService.isActive) { buffer.write('Active'); } else { buffer.write('Inactive'); } buffer.write('
'); buffer.write('Version: '); buffer.write(AnalysisServer.VERSION); buffer.write('

'); buffer.write('

Performance Data

'); buffer.write('

'); buffer.write(makeLink( COMMUNICATION_PERFORMANCE_PATH, {}, 'Communication performance')); buffer.write('

'); }, (StringBuffer buffer) { _writeSubscriptionList(buffer, ServerService.VALUES, services); }); return true; } /** * Write a representation of the given [stackTrace] to the given [buffer]. */ void _writeStackTrace(StringBuffer buffer, StackTrace stackTrace) { if (stackTrace != null) { String trace = stackTrace.toString().replaceAll('#', '
#'); if (trace.startsWith('
#')) { trace = trace.substring(4); } buffer.write('

'); buffer.write(trace); buffer.write('

'); } } /** * Given a [service] that could be subscribed to and a set of the services * that are actually subscribed to ([subscribedServices]), write a * representation of the service to the given [buffer]. */ void _writeSubscriptionInList( StringBuffer buffer, Enum service, Set subscribedServices) { if (subscribedServices.contains(service)) { buffer.write('+ '); } else { buffer.write('- '); } buffer.write(service.name); buffer.write('
'); } /** * Given a [service] that could be subscribed to and a set of paths that are * subscribed to the services ([subscribedPaths]), write a representation of * the service to the given [buffer]. */ void _writeSubscriptionInMap( StringBuffer buffer, Enum service, Set subscribedPaths) { buffer.write('

'); buffer.write(service.name); buffer.write('

'); if (subscribedPaths == null || subscribedPaths.isEmpty) { buffer.write('none'); } else { List paths = subscribedPaths.toList(); paths.sort(); for (String path in paths) { buffer.write('

'); buffer.write(path); buffer.write('

'); } } } /** * Given a list containing all of the services that can be subscribed to in a * single domain ([allServices]) and a set of the services that are actually * subscribed to ([subscribedServices]), write a representation of the * subscriptions to the given [buffer]. */ void _writeSubscriptionList(StringBuffer buffer, List allServices, Set subscribedServices) { buffer.write('

Subscriptions

'); buffer.write('

'); for (Enum service in allServices) { _writeSubscriptionInList(buffer, service, subscribedServices); } buffer.write('

'); } /** * Given a list containing all of the services that can be subscribed to in a * single domain ([allServices]) and a set of the services that are actually * subscribed to ([subscribedServices]), write a representation of the * subscriptions to the given [buffer]. */ void _writeSubscriptionMap(StringBuffer buffer, List allServices, Map> subscribedServices) { buffer.write('

Subscriptions

'); for (Enum service in allServices) { _writeSubscriptionInMap(buffer, service, subscribedServices[service]); } } /** * Write two columns of information to the given [buffer], where the * [leftColumn] and [rightColumn] functions are used to generate the content * of those columns. */ void _writeTwoColumns(StringBuffer buffer, HtmlGenerator leftColumn, HtmlGenerator rightColumn) { buffer .write('
'); leftColumn(buffer); buffer.write(''); rightColumn(buffer); buffer.write('
'); } /** * Render the given [value] as HTML and append it to the given [buffer]. The * [linkParameters] will be used if the value is too large to be displayed on * the current page and needs to be linked to a separate page. */ void _writeValueAsHtml( StringBuffer buffer, Object value, Map linkParameters) { if (value == null) { buffer.write('null'); } else if (value is String) { buffer.write('
${HTML_ESCAPE.convert(value)}
'); } else if (value is List) { buffer.write('List containing ${value.length} entries'); buffer.write('
    '); for (var entry in value) { buffer.write('
  • '); _writeValueAsHtml(buffer, entry, linkParameters); buffer.write('
  • '); } buffer.write('
'); } else if (value is AstNode) { String link = makeLink(AST_PATH, linkParameters, value.runtimeType.toString()); buffer.write('$link'); } else if (value is Element) { String link = makeLink(ELEMENT_PATH, linkParameters, value.runtimeType.toString()); buffer.write('$link'); } else { buffer.write(HTML_ESCAPE.convert(value.toString())); buffer.write(' (${value.runtimeType.toString()})'); } } /** * Create a link to [path] with query parameters [params], with inner HTML * [innerHtml]. If [hasError] is `true`, then the link will have the class * 'error'. */ static String makeLink( String path, Map params, String innerHtml, [bool hasError = false]) { Uri uri = new Uri(path: path, queryParameters: params); String href = HTML_ESCAPE.convert(uri.toString()); String classAttribute = hasError ? ' class="error"' : ''; return '$innerHtml'; } }