blob: 23126f95e57093525b05483a43fe0195d8c065fd [file] [log] [blame]
// 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.status.get_handler;
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:analysis_server/plugin/protocol/protocol.dart' hide Element;
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/domain_completion.dart';
import 'package:analysis_server/src/domain_diagnostic.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/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:analysis_server/src/status/validator.dart';
import 'package:analysis_server/src/utilities/average.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/error_processor.dart';
import 'package:analyzer/src/context/cache.dart';
import 'package:analyzer/src/context/context.dart' show AnalysisContextImpl;
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/error.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_collection.dart';
import 'package:analyzer/src/generated/utilities_general.dart';
import 'package:analyzer/src/services/lint.dart';
import 'package:analyzer/src/task/dart.dart';
import 'package:analyzer/src/task/driver.dart';
import 'package:analyzer/src/task/html.dart';
import 'package:analyzer/src/task/options.dart';
import 'package:analyzer/task/dart.dart';
import 'package:analyzer/task/general.dart';
import 'package:analyzer/task/html.dart';
import 'package:analyzer/task/model.dart';
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 diagnostic information for a single context.
*/
static const String CONTEXT_DIAGNOSTICS_PATH = '/diagnostic/context';
/**
* The path used to request running a validation report for a single context.
*/
static const String CONTEXT_VALIDATION_DIAGNOSTICS_PATH =
'/diagnostic/contextValidation';
/**
* The path used to request information about a specific context.
*/
static const String CONTEXT_PATH = '/context';
/**
* The path used to request diagnostic information.
*/
static const String DIAGNOSTIC_PATH = '/diagnostic';
/**
* 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");
/**
* Rolling average of calls to get diagnostics.
*/
Average _diagnosticCallAverage = new Average();
/**
* The socket server whose status is to be reported on.
*/
SocketServer _server;
/**
* Buffer containing strings printed by the analysis server.
*/
List<String> _printBuffer;
/**
* Contents of overlay files.
*/
final Map<String, String> _overlayContents = <String, String>{};
/**
* Handler for diagnostics requests.
*/
DiagnosticDomainHandler _diagnosticHandler;
/**
* Initialize a newly created handler for GET requests.
*/
GetHandler(this._server, this._printBuffer);
DiagnosticDomainHandler get diagnosticHandler {
if (_diagnosticHandler == null) {
_diagnosticHandler = new DiagnosticDomainHandler(_server.analysisServer);
}
return _diagnosticHandler;
}
/**
* 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 == '/' || 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_DIAGNOSTICS_PATH) {
_returnContextDiagnostics(request);
} else if (path == CONTEXT_VALIDATION_DIAGNOSTICS_PATH) {
_returnContextValidationDiagnostics(request);
} else if (path == CONTEXT_PATH) {
_returnContextInfo(request);
} else if (path == DIAGNOSTIC_PATH) {
_returnDiagnosticInfo(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 any AST structure stored in the given [entry].
*/
CompilationUnit _getAnyAst(CacheEntry entry) {
CompilationUnit unit = entry.getValue(PARSED_UNIT);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT1);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT2);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT3);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT4);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT5);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT6);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT7);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT8);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT9);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT10);
if (unit != null) {
return unit;
}
unit = entry.getValue(RESOLVED_UNIT11);
if (unit != null) {
return unit;
}
return entry.getValue(RESOLVED_UNIT);
}
/**
* Return a list of the result descriptors whose state should be displayed for
* the given cache [entry].
*/
List<ResultDescriptor> _getExpectedResults(CacheEntry entry) {
AnalysisTarget target = entry.target;
Set<ResultDescriptor> results = entry.nonInvalidResults.toSet();
if (target is Source) {
String name = target.shortName;
results.add(CONTENT);
results.add(LINE_INFO);
results.add(MODIFICATION_TIME);
if (AnalysisEngine.isDartFileName(name)) {
results.add(BUILD_DIRECTIVES_ERRORS);
results.add(BUILD_LIBRARY_ERRORS);
results.add(CONTAINING_LIBRARIES);
results.add(DART_ERRORS);
results.add(EXPLICITLY_IMPORTED_LIBRARIES);
results.add(EXPORT_SOURCE_CLOSURE);
results.add(EXPORTED_LIBRARIES);
results.add(IMPORTED_LIBRARIES);
results.add(INCLUDED_PARTS);
results.add(IS_LAUNCHABLE);
results.add(LIBRARY_ELEMENT1);
results.add(LIBRARY_ELEMENT2);
results.add(LIBRARY_ELEMENT3);
results.add(LIBRARY_ELEMENT4);
results.add(LIBRARY_ELEMENT5);
results.add(LIBRARY_ELEMENT);
results.add(LIBRARY_ERRORS_READY);
results.add(PARSE_ERRORS);
results.add(PARSED_UNIT);
results.add(REFERENCED_NAMES);
results.add(SCAN_ERRORS);
results.add(SOURCE_KIND);
results.add(TOKEN_STREAM);
results.add(UNITS);
} else if (AnalysisEngine.isHtmlFileName(name)) {
results.add(DART_SCRIPTS);
results.add(HTML_DOCUMENT);
results.add(HTML_DOCUMENT_ERRORS);
results.add(HTML_ERRORS);
results.add(REFERENCED_LIBRARIES);
} else if (AnalysisEngine.isAnalysisOptionsFileName(name)) {
results.add(ANALYSIS_OPTIONS_ERRORS);
}
} else if (target is LibrarySpecificUnit) {
results.add(COMPILATION_UNIT_CONSTANTS);
results.add(COMPILATION_UNIT_ELEMENT);
results.add(HINTS);
results.add(LINTS);
results.add(INFERABLE_STATIC_VARIABLES_IN_UNIT);
results.add(LIBRARY_UNIT_ERRORS);
results.add(RESOLVE_TYPE_NAMES_ERRORS);
results.add(RESOLVE_UNIT_ERRORS);
results.add(RESOLVED_UNIT1);
results.add(RESOLVED_UNIT2);
results.add(RESOLVED_UNIT3);
results.add(RESOLVED_UNIT4);
results.add(RESOLVED_UNIT5);
results.add(RESOLVED_UNIT6);
results.add(RESOLVED_UNIT7);
results.add(RESOLVED_UNIT8);
results.add(RESOLVED_UNIT9);
results.add(RESOLVED_UNIT10);
results.add(RESOLVED_UNIT11);
results.add(RESOLVED_UNIT);
results.add(STRONG_MODE_ERRORS);
results.add(USED_IMPORTED_ELEMENTS);
results.add(USED_LOCAL_ELEMENTS);
results.add(VARIABLE_REFERENCE_ERRORS);
results.add(VERIFY_ERRORS);
} else if (target is ConstantEvaluationTarget) {
results.add(CONSTANT_DEPENDENCIES);
results.add(CONSTANT_VALUE);
if (target is VariableElement) {
results.add(INFERABLE_STATIC_VARIABLE_DEPENDENCIES);
results.add(INFERRED_STATIC_VARIABLE);
}
} else if (target is AnalysisContextTarget) {
results.add(TYPE_PROVIDER);
}
return results.toList();
}
/**
* Return `true` if the given analysis [context] has at least one entry with
* an exception.
*/
bool _hasException(InternalAnalysisContext context) {
MapIterator<AnalysisTarget, CacheEntry> iterator =
context.analysisCache.iterator();
while (iterator.moveNext()) {
CacheEntry entry = iterator.value;
if (entry == null || entry.exception != null) {
return true;
}
}
return false;
}
/**
* Return the folder in the [folderMap] with which the given [context] is
* associated.
*/
Folder _keyForValue(
Map<Folder, AnalysisContext> 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('<h3>Analysis Performance</h3>');
_writeTwoColumns(buffer, (StringBuffer buffer) {
//
// Write performance tags.
//
buffer.write('<p><b>Performance tag data</b></p>');
buffer.write(
'<table style="border-collapse: separate; border-spacing: 10px 5px;">');
_writeRow(buffer, ['Time (in ms)', 'Percent', 'Tag name'],
header: true);
// prepare sorted tags
List<PerformanceTag> tags = PerformanceTag.all.toList();
tags.remove(ServerPerformanceStatistics.idle);
tags.sort((a, b) => b.elapsedMs - a.elapsedMs);
// prepare total time
int totalTagTime = 0;
tags.forEach((PerformanceTag tag) {
totalTagTime += tag.elapsedMs;
});
// write rows
void writeRow(PerformanceTag tag) {
double percent = (tag.elapsedMs * 100) / totalTagTime;
String percentStr = '${percent.toStringAsFixed(2)}%';
_writeRow(buffer, [tag.elapsedMs, percentStr, tag.label],
classes: ["right", "right", null]);
}
tags.forEach(writeRow);
buffer.write('</table>');
//
// Write target counts.
//
void incrementCount(Map<String, int> counts, String key) {
int count = counts[key];
if (count == null) {
count = 1;
} else {
count++;
}
counts[key] = count;
}
Set<AnalysisTarget> countedTargets = new HashSet<AnalysisTarget>();
Map<String, int> sourceTypeCounts = new HashMap<String, int>();
Map<String, int> typeCounts = new HashMap<String, int>();
analysisServer.folderMap
.forEach((Folder folder, InternalAnalysisContext context) {
AnalysisCache cache = context.analysisCache;
MapIterator<AnalysisTarget, CacheEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
AnalysisTarget target = iterator.key;
if (countedTargets.add(target)) {
if (target is Source) {
String name = target.fullName;
String sourceName;
if (AnalysisEngine.isDartFileName(name)) {
if (iterator.value.explicitlyAdded) {
sourceName = 'Dart file (explicit)';
} else {
sourceName = 'Dart file (implicit)';
}
} else if (AnalysisEngine.isHtmlFileName(name)) {
if (iterator.value.explicitlyAdded) {
sourceName = 'Html file (explicit)';
} else {
sourceName = 'Html file (implicit)';
}
} else {
if (iterator.value.explicitlyAdded) {
sourceName = 'Unknown file (explicit)';
} else {
sourceName = 'Unknown file (implicit)';
}
}
incrementCount(sourceTypeCounts, sourceName);
} else if (target is ConstantEvaluationTarget) {
incrementCount(typeCounts, 'ConstantEvaluationTarget');
} else {
String typeName = target.runtimeType.toString();
incrementCount(typeCounts, typeName);
}
}
}
});
List<String> sourceTypeNames = sourceTypeCounts.keys.toList();
sourceTypeNames.sort();
List<String> typeNames = typeCounts.keys.toList();
typeNames.sort();
buffer.write('<p><b>Target counts</b></p>');
buffer.write(
'<table style="border-collapse: separate; border-spacing: 10px 5px;">');
_writeRow(buffer, ['Target', 'Count'], header: true);
for (String sourceTypeName in sourceTypeNames) {
_writeRow(
buffer, [sourceTypeName, sourceTypeCounts[sourceTypeName]],
classes: [null, "right"]);
}
for (String typeName in typeNames) {
_writeRow(buffer, [typeName, typeCounts[typeName]],
classes: [null, "right"]);
}
buffer.write('</table>');
}, (StringBuffer buffer) {
//
// Write task model timing information.
//
buffer.write('<p><b>Task performance data</b></p>');
buffer.write(
'<table style="border-collapse: separate; border-spacing: 10px 5px;">');
_writeRow(
buffer,
[
'Task Name',
'Count',
'Total Time (in ms)',
'Average Time (in ms)'
],
header: true);
Map<Type, int> countMap = AnalysisTask.countMap;
Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap;
List<Type> taskClasses = stopwatchMap.keys.toList();
taskClasses.sort((Type first, Type second) =>
first.toString().compareTo(second.toString()));
int totalTaskTime = 0;
taskClasses.forEach((Type taskClass) {
int count = countMap[taskClass];
if (count == null) {
count = 0;
}
int taskTime = stopwatchMap[taskClass].elapsedMilliseconds;
totalTaskTime += taskTime;
_writeRow(buffer, [
taskClass.toString(),
count,
taskTime,
count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3)
], classes: [
null,
"right",
"right",
"right"
]);
});
_writeRow(buffer, ['Total', '-', totalTaskTime, '-'],
classes: [null, "right", "right", "right"]);
buffer.write('</table>');
});
});
});
}
/**
* 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');
}
InternalAnalysisContext 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('<p>Not found.</p>');
return;
}
List<Source> libraries = context.getLibrariesContaining(source);
for (Source library in libraries) {
AnalysisTarget target = new LibrarySpecificUnit(library, source);
CacheEntry entry = context.analysisCache.get(target);
buffer.write('<b>$target</b><br>');
if (entry == null) {
buffer.write('<p>Not found.</p>');
continue;
}
CompilationUnit ast = _getAnyAst(entry);
if (ast == null) {
buffer.write('<p>null</p>');
continue;
}
AstWriter writer = new AstWriter(buffer);
ast.accept(writer);
if (writer.exceptions.isNotEmpty) {
buffer.write('<h3>Exceptions while creating page</h3>');
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<Folder> allContexts = <Folder>[];
Map<Folder, List<CacheEntry>> entryMap =
new HashMap<Folder, List<CacheEntry>>();
StringBuffer invalidKeysBuffer = new StringBuffer();
analysisServer.folderMap
.forEach((Folder folder, InternalAnalysisContext context) {
Source source = context.sourceFactory.forUri(sourceUri);
if (source != null) {
MapIterator<AnalysisTarget, CacheEntry> iterator =
context.analysisCache.iterator();
while (iterator.moveNext()) {
if (source == iterator.key.source) {
if (!allContexts.contains(folder)) {
allContexts.add(folder);
}
List<CacheEntry> entries = entryMap[folder];
if (entries == null) {
entries = <CacheEntry>[];
entryMap[folder] = entries;
}
CacheEntry value = iterator.value;
if (value == null) {
if (invalidKeysBuffer.isNotEmpty) {
invalidKeysBuffer.write(', ');
}
invalidKeysBuffer.write(iterator.key.toString());
} else {
entries.add(value);
}
}
}
}
});
allContexts.sort((Folder firstFolder, Folder secondFolder) =>
firstFolder.path.compareTo(secondFolder.path));
InternalAnalysisContext context = analysisServer.folderMap[folder];
_writeResponse(request, (StringBuffer buffer) {
_writePage(buffer, 'Analysis Server - Cache Entry',
['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) {
if (invalidKeysBuffer.isNotEmpty) {
buffer.write('<h3>Targets with null Entries</h3><p>');
buffer.write(invalidKeysBuffer.toString());
buffer.write('</p>');
}
List<CacheEntry> entries = entryMap[folder];
buffer.write('<h3>Analyzing Contexts</h3><p>');
bool first = true;
allContexts.forEach((Folder folder) {
if (first) {
first = false;
} else {
buffer.write('<br>');
}
InternalAnalysisContext 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 (entries == null) {
buffer.write(' (file does not exist)');
} else {
CacheEntry sourceEntry = entries
.firstWhere((CacheEntry entry) => entry.target is Source);
if (sourceEntry == null) {
buffer.write(' (missing source entry)');
} else if (sourceEntry.explicitlyAdded) {
buffer.write(' (explicit)');
} else {
buffer.write(' (implicit)');
}
}
});
buffer.write('</p>');
if (entries == null) {
buffer.write('<p>Not being analyzed in this context.</p>');
return;
}
for (CacheEntry entry in entries) {
Map<String, String> linkParameters = <String, String>{
CONTEXT_QUERY_PARAM: folder.path,
SOURCE_QUERY_PARAM: sourceUri
};
List<ResultDescriptor> results = _getExpectedResults(entry);
results.sort((ResultDescriptor first, ResultDescriptor second) =>
first.toString().compareTo(second.toString()));
buffer.write('<h3>');
buffer.write(HTML_ESCAPE.convert(entry.target.toString()));
buffer.write('</h3>');
buffer.write('<p>time</p><blockquote><p>Value</p><blockquote>');
buffer.write(entry.modificationTime);
buffer.write('</blockquote></blockquote>');
for (ResultDescriptor result in results) {
ResultData data = entry.getResultData(result);
CacheState state = entry.getState(result);
String descriptorName = HTML_ESCAPE.convert(result.toString());
String descriptorState = HTML_ESCAPE.convert(state.toString());
buffer
.write('<p>$descriptorName ($descriptorState)</p><blockquote>');
if (state == CacheState.VALID) {
buffer.write('<p>Value</p><blockquote>');
try {
_writeValueAsHtml(
buffer, entry.getValue(result), linkParameters);
} catch (exception) {
buffer.write('(${HTML_ESCAPE.convert(exception.toString())})');
}
buffer.write('</blockquote>');
}
_writeTargetedResults(buffer, 'Depends on', data.dependedOnResults);
_writeTargetedResults(
buffer, 'Depended on by', data.dependentResults);
buffer.write('</blockquote>');
}
if (entry.exception != null) {
buffer.write('<dt>exception</dt><dd>');
_writeException(buffer, entry.exception);
buffer.write('</dd>');
}
}
});
});
}
/**
* 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');
}
// TODO(brianwilkerson) Figure out how to convert the 'descriptorFilter' to
// a ResultDescriptor so that we can query the state, then uncomment the
// code below that computes and prints the list of links.
// Folder folder = _findFolder(analysisServer, contextFilter);
// InternalAnalysisContext context = analysisServer.folderMap[folder];
// List<String> links = <String>[];
// MapIterator<AnalysisTarget, CacheEntry> iterator = context.analysisCache.iterator();
// while (iterator.moveNext()) {
// Source source = iterator.key.source;
// if (source != null) {
// CacheEntry entry = iterator.value;
// if (entry.getState(result) == stateFilter) {
// 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('<p>Cache search is not yet implemented.</p>');
// buffer.write('<p>${links.length} files found</p>');
// buffer.write('<ul>');
// links.forEach((String link) {
// buffer.write('<li>$link</li>');
// });
// buffer.write('</ul>');
});
});
}
/**
* 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('<h3>Communication Performance</h3>');
_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('<h4>Startup</h4>');
buffer.write('<table>');
_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('</table>');
}, (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('<h4>Current</h4>');
buffer.write('<table>');
_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('</table>');
});
});
});
}
/**
* 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 displaying diagnostic information for a single context.
*/
void _returnContextDiagnostics(HttpRequest request) {
AnalysisServer analysisServer = _server.analysisServer;
if (analysisServer == null) {
return _returnFailure(request, 'Analysis server is 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');
}
InternalAnalysisContext context = analysisServer.folderMap[folder];
_writeResponse(request, (StringBuffer buffer) {
_writePage(buffer, 'Analysis Server - Context Diagnostics',
['Context: $contextFilter'], (StringBuffer buffer) {
_writeContextDiagnostics(buffer, context);
});
});
}
/**
* 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<String> priorityNames;
List<String> explicitNames = <String>[];
List<String> implicitNames = <String>[];
Map<String, String> links = new HashMap<String, String>();
List<CaughtException> exceptions = <CaughtException>[];
InternalAnalysisContext context = analysisServer.folderMap[folder];
priorityNames = context.prioritySources
.map((Source source) => source.fullName)
.toList();
MapIterator<AnalysisTarget, CacheEntry> iterator =
context.analysisCache.iterator(context: context);
while (iterator.moveNext()) {
AnalysisTarget target = iterator.key;
if (target is Source) {
CacheEntry entry = iterator.value;
String sourceName = target.fullName;
if (!links.containsKey(sourceName)) {
CaughtException exception = entry.exception;
if (exception != null) {
exceptions.add(exception);
}
String link = makeLink(
CACHE_ENTRY_PATH,
{
CONTEXT_QUERY_PARAM: folder.path,
SOURCE_QUERY_PARAM: target.uri.toString()
},
sourceName,
exception != null);
if (entry.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<String> fileNames) {
buffer.write('<h3>$title</h3>');
if (fileNames == null || fileNames.isEmpty) {
buffer.write('<p>None</p>');
} else {
buffer.write('<p><table style="width: 100%">');
for (String fileName in fileNames) {
buffer.write('<tr><td>');
buffer.write(links[fileName]);
buffer.write('</td><td>');
if (_overlayContents.containsKey(fileName)) {
buffer.write(
makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay'));
}
buffer.write('</td></tr>');
}
buffer.write('</table></p>');
}
}
_writeResponse(request, (StringBuffer buffer) {
_writePage(
buffer, 'Analysis Server - Context', ['Context: $contextFilter'],
(StringBuffer buffer) {
buffer.write('<h3>Configuration</h3>');
_writeTwoColumns(buffer, (StringBuffer buffer) {
AnalysisOptionsImpl options = context.analysisOptions;
buffer.write('<p><b>Options</b></p>');
buffer.write('<p>');
_writeOption(
buffer, 'Analyze functon bodies', options.analyzeFunctionBodies);
_writeOption(buffer, 'Cache size', options.cacheSize);
_writeOption(
buffer, 'Enable generic methods', options.enableGenericMethods);
_writeOption(buffer, 'Enable strict call checks',
options.enableStrictCallChecks);
_writeOption(
buffer, 'Enable super mixins', options.enableSuperMixins);
_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, 'Generate hints', options.hint);
_writeOption(buffer, 'Incremental resolution', options.incremental);
_writeOption(buffer, 'Incremental resolution with API changes',
options.incrementalApi);
_writeOption(buffer, 'Preserve comments', options.preserveComments);
_writeOption(buffer, 'Strong mode', options.strongMode);
_writeOption(buffer, 'Strong mode hints', options.strongModeHints,
last: true);
buffer.write('</p>');
}, (StringBuffer buffer) {
List<Linter> lints =
context.getConfigurationData(CONFIGURED_LINTS_KEY);
buffer.write('<p><b>Lints</b></p>');
if (lints.isEmpty) {
buffer.write('<p>none</p>');
} else {
for (Linter lint in lints) {
buffer.write('<p>');
buffer.write(lint.runtimeType);
buffer.write('</p>');
}
}
List<ErrorProcessor> errorProcessors =
context.getConfigurationData(CONFIGURED_ERROR_PROCESSORS);
int processorCount = errorProcessors?.length ?? 0;
buffer.write('<p><b>Error Processor count</b>: $processorCount</p>');
});
_writeFiles(buffer, 'Priority Files', priorityNames);
_writeFiles(buffer, 'Explicitly Analyzed Files', explicitNames);
_writeFiles(buffer, 'Implicitly Analyzed Files', implicitNames);
buffer.write('<h3>Exceptions</h3>');
if (exceptions.isEmpty) {
buffer.write('<p>none</p>');
} else {
exceptions.forEach((CaughtException exception) {
_writeException(buffer, exception);
});
}
buffer.write('<h3>Targets Without Entries</h3>');
bool foundEntry = false;
MapIterator<AnalysisTarget, CacheEntry> iterator =
context.analysisCache.iterator(context: context);
while (iterator.moveNext()) {
if (iterator.value == null) {
foundEntry = true;
buffer.write('<p>');
buffer.write(iterator.key.toString());
buffer.write(' (');
buffer.write(iterator.key.runtimeType.toString());
buffer.write(')</p>');
}
}
if (!foundEntry) {
buffer.write('<p>none</p>');
}
});
});
}
/**
* Return a response displaying the results of running a validation report on
* a single context.
*/
void _returnContextValidationDiagnostics(HttpRequest request) {
AnalysisServer analysisServer = _server.analysisServer;
if (analysisServer == null) {
return _returnFailure(request, 'Analysis server is 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');
}
InternalAnalysisContext context = analysisServer.folderMap[folder];
_writeResponse(request, (StringBuffer buffer) {
_writePage(buffer, 'Analysis Server - Context Validation Diagnostics',
['Context: $contextFilter'], (StringBuffer buffer) {
_writeContextValidationDiagnostics(buffer, context);
});
});
}
/**
* Return a response displaying diagnostic information.
*/
void _returnDiagnosticInfo(HttpRequest request) {
_writeResponse(request, (StringBuffer buffer) {
_writePage(buffer, 'Analysis Server - Diagnostic info', [],
(StringBuffer buffer) {
_writeDiagnosticStatus(buffer);
});
});
}
/**
* 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');
}
InternalAnalysisContext 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('<p>Not found.</p>');
return;
}
CacheEntry entry = context.analysisCache.get(source);
if (entry == null) {
buffer.write('<p>Not found.</p>');
return;
}
LibraryElement element = entry.getValue(LIBRARY_ELEMENT);
if (element == null) {
buffer.write('<p>null</p>');
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<String>, List<InspectLocation>> relations =
await index.findElementsByName(name);
_writeResponse(request, (StringBuffer buffer) {
_writePage(buffer, 'Analysis Server - Index Elements', ['Name: $name'],
(StringBuffer buffer) {
buffer.write('<table border="1">');
_writeRow(buffer, ['Element', 'Relationship', 'Location'],
header: true);
relations.forEach(
(List<String> elementPath, List<InspectLocation> 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('</table>');
});
});
} 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('<pre>${HTML_ESCAPE.convert(contents)}</pre>');
});
});
}
/**
* 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('<table border="1">');
_overlayContents.clear();
ContentCache overlayState = analysisServer.overlayState;
overlayState.accept((String fullName, int stamp, String contents) {
buffer.write('<tr>');
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('<tr><td colspan="2">Total: $count entries.</td></tr>');
buffer.write('</table>');
});
});
}
/**
* 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('<h3>Unknown page: ');
buffer.write(request.uri.path);
buffer.write('</h3>');
buffer.write('''
<p>
You have reached an un-recognized page. If you reached this page by
following a link from a status page, please report the broken link to
the Dart analyzer team:
<a>https://github.com/dart-lang/sdk/issues/new</a>.
</p><p>
If you mistyped the URL, you can correct it or return to
${makeLink(STATUS_PATH, {}, 'the main status page')}.
</p>''');
});
});
}
/**
* 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<Folder, AnalysisContext> folderMap = analysisServer.folderMap;
List<Folder> folders = folderMap.keys.toList();
folders.sort((Folder first, Folder second) =>
first.shortName.compareTo(second.shortName));
ServerOperationQueue operationQueue = analysisServer.operationQueue;
buffer.write('<h3>Analysis Domain</h3>');
_writeTwoColumns(buffer, (StringBuffer buffer) {
if (operationQueue.isEmpty) {
buffer.write('<p>Status: Done analyzing</p>');
} else {
ServerOperation operation = operationQueue.peek();
if (operation is PerformAnalysisOperation) {
Folder folder = _keyForValue(folderMap, operation.context);
if (folder == null) {
buffer.write('<p>Status: Analyzing in unmapped context</p>');
} else {
buffer.write('<p>Status: Analyzing in ${folder.path}</p>');
}
} else {
buffer.write('<p>Status: Analyzing</p>');
}
}
buffer.write('<p>');
buffer.write(makeLink(OVERLAYS_PATH, {}, 'All overlay information'));
buffer.write('</p>');
buffer.write('<p><b>Analysis Contexts</b></p>');
buffer.write('<p>');
bool first = true;
folders.forEach((Folder folder) {
if (first) {
first = false;
} else {
buffer.write('<br>');
}
String key = folder.shortName;
buffer.write(makeLink(CONTEXT_PATH, {CONTEXT_QUERY_PARAM: folder.path},
key, _hasException(folderMap[folder])));
buffer.write(' <small><b>[');
buffer.write(makeLink(CONTEXT_DIAGNOSTICS_PATH,
{CONTEXT_QUERY_PARAM: folder.path}, 'diagnostics'));
buffer.write(']</b></small>');
if (!folder.getChild('.packages').exists) {
buffer.write(' <small>[no .packages file]</small>');
}
});
// TODO(brianwilkerson) Add items for the SDK contexts (currently only one).
buffer.write('</p>');
int freq = AnalysisServer.performOperationDelayFreqency;
String delay = freq > 0 ? '1 ms every $freq ms' : 'off';
buffer.write('<p><b>Performance Data</b></p>');
buffer.write('<p>Perform operation delay: $delay</p>');
buffer.write('<p>');
buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data'));
buffer.write('</p>');
}, (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<CompletionPerformance> list = handler.performanceList;
if (list != null && list.isNotEmpty) {
performance = list[max(0, min(list.length - 1, index))];
}
}
if (performance == null) {
buffer.write('<h3>Completion Performance Detail</h3>');
buffer.write('<p>No completions yet</p>');
return;
}
buffer.write('<h3>Completion Performance Detail</h3>');
buffer.write('<p>${performance.startTimeAndMs} for ${performance.source}');
buffer.write('<table>');
_writeRow(buffer, ['Elapsed', '', 'Operation'], header: true);
performance.operations.forEach((OperationPerformance op) {
String elapsed = op.elapsed != null ? op.elapsed.toString() : '???';
_writeRow(buffer, [elapsed, '&nbsp;&nbsp;', op.name]);
});
buffer.write('</table>');
buffer.write('<p><b>Compute Cache Performance</b>: ');
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('</p>');
}
/**
* 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('<h3>Completion Performance List</h3>');
if (handler == null) {
return;
}
buffer.write('<table>');
_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,
'&nbsp;&nbsp;',
performance.firstNotificationInMilliseconds,
'&nbsp;&nbsp;',
performance.elapsedInMilliseconds,
'&nbsp;&nbsp;',
performance.notificationCount,
'&nbsp;&nbsp;',
performance.suggestionCount,
'&nbsp;&nbsp;',
HTML_ESCAPE.convert(performance.snippet)
]);
++index;
}
buffer.write('</table>');
buffer.write('''
<p><strong>First (ms)</strong> - 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.
<p><strong>Complete (ms)</strong> - 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.
<p><strong># Notifications</strong> - the total number of notifications
sent to the client with completion results for this request.
<p><strong># Suggestions</strong> - 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.''');
}
/**
* Write diagnostic information about the given [context] to the given
* [buffer].
*/
void _writeContextDiagnostics(
StringBuffer buffer, InternalAnalysisContext context) {
AnalysisDriver driver = (context as AnalysisContextImpl).driver;
List<WorkItem> workItems = driver.currentWorkOrder?.workItems;
buffer.write('<p>');
buffer.write(makeLink(CONTEXT_VALIDATION_DIAGNOSTICS_PATH,
{CONTEXT_QUERY_PARAM: context.name}, 'Run validation'));
buffer.write('</p>');
buffer.write('<h3>Most Recently Perfomed Tasks</h3>');
AnalysisTask.LAST_TASKS.forEach((String description) {
buffer.write('<p>');
buffer.write(description);
buffer.write('</p>');
});
void writeWorkItem(StringBuffer buffer, WorkItem item) {
if (item == null) {
buffer.write('none');
} else {
buffer.write(item.descriptor?.name);
buffer.write(' computing ');
buffer.write(item.spawningResult?.name);
buffer.write(' for ');
buffer.write(item.target);
}
}
buffer.write('<h3>Work Items</h3>');
buffer.write('<p><b>Current:</b> ');
writeWorkItem(buffer, driver.currentWorkOrder?.current);
buffer.write('</p>');
if (workItems != null) {
workItems.reversed.forEach((WorkItem item) {
buffer.write('<p>');
writeWorkItem(buffer, item);
buffer.write('</p>');
});
}
}
/**
* Write diagnostic information about the given [context] to the given
* [buffer].
*/
void _writeContextValidationDiagnostics(
StringBuffer buffer, InternalAnalysisContext context) {
Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
ValidationResults results = new ValidationResults(context);
stopwatch.stop();
buffer.write('<h3>Validation Results</h3>');
buffer.write('<p>Re-analysis took ');
buffer.write(stopwatch.elapsedMilliseconds);
buffer.write(' ms</p>');
results.writeOn(buffer);
}
/**
* Write the status of the diagnostic domain to the given [buffer].
*/
void _writeDiagnosticStatus(StringBuffer buffer) {
var request = new DiagnosticGetDiagnosticsParams().toRequest('0');
var stopwatch = new Stopwatch();
stopwatch.start();
var response = diagnosticHandler.handleRequest(request);
stopwatch.stop();
int elapsedMs = stopwatch.elapsedMilliseconds;
_diagnosticCallAverage.addSample(elapsedMs);
buffer.write('<h3>Timing</h3>');
buffer.write('<p>getDiagnostic (last call): ');
buffer.write(elapsedMs);
buffer.write(' (ms)</p>');
buffer.write('<p>getDiagnostic (rolling average): ');
buffer.write(_diagnosticCallAverage.value);
buffer.write(' (ms)</p>&nbsp;');
var json = response.toJson()[Response.RESULT];
List contexts = json['contexts'];
contexts.sort((first, second) => first['name'].compareTo(second['name']));
for (var context in contexts) {
buffer.write('<p><h3>');
buffer.write(context['name']);
buffer.write('</h3></p>');
buffer.write('<p>explicitFileCount: ');
buffer.write(context['explicitFileCount']);
buffer.write('</p>');
buffer.write('<p>implicitFileCount: ');
buffer.write(context['implicitFileCount']);
buffer.write('</p>');
buffer.write('<p>workItemQueueLength: ');
buffer.write(context['workItemQueueLength']);
buffer.write('</p>');
buffer.write('<p>workItemQueueLengthAverage: ');
buffer.write(context['workItemQueueLengthAverage']);
buffer.write('</p>');
}
}
/**
* Write the status of the edit domain (on the main status page) to the given
* [buffer].
*/
void _writeEditStatus(StringBuffer buffer) {
buffer.write('<h3>Edit Domain</h3>');
_writeTwoColumns(buffer, (StringBuffer buffer) {
buffer.write('<p><b>Performance Data</b></p>');
buffer.write('<p>');
buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data'));
buffer.write('</p>');
}, (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('<p>');
if (isCause) {
buffer.write('Caused by ');
}
buffer.write(exception.message);
buffer.write('</p>');
_writeStackTrace(buffer, caughtException.stackTrace);
CaughtException cause = exception.cause;
if (cause != null) {
buffer.write('<blockquote>');
_writeException(buffer, cause, isCause: true);
buffer.write('</blockquote>');
}
} else {
buffer.write('<p>');
if (isCause) {
buffer.write('Caused by ');
}
buffer.write(exception.toString());
buffer.write('<p>');
_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<ExecutionService> services = new Set<ExecutionService>();
if (handler.onFileAnalyzed != null) {
services.add(ExecutionService.LAUNCH_DATA);
}
if (handler != null) {
buffer.write('<h3>Execution Domain</h3>');
_writeTwoColumns(buffer, (StringBuffer buffer) {
_writeSubscriptionList(buffer, ExecutionService.VALUES, services);
}, (StringBuffer buffer) {});
}
}
void _writeListItem(StringBuffer buffer, writer()) {
buffer.write('<li>');
writer();
buffer.write('</li>');
}
void _writeListOfStrings(
StringBuffer buffer, String listName, Iterable<String> items) {
List<String> itemList = items.toList();
itemList.sort((String a, String b) {
a = a.toLowerCase();
b = b.toLowerCase();
return a.compareTo(b);
});
buffer.write('List "$listName" containing ${itemList.length} entries:');
buffer.write('<ul>');
for (String member in itemList) {
_writeListItem(buffer, () {
buffer.write(member);
});
}
buffer.write('</ul>');
}
/**
* 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('<br>');
}
}
/**
* 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<String> 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('<!DOCTYPE html>');
buffer.write('<html>');
buffer.write('<head>');
buffer.write('<meta charset="utf-8">');
buffer.write(
'<meta name="viewport" content="width=device-width, initial-scale=1.0">');
buffer.write('<title>$title</title>');
buffer.write('<style>');
buffer.write('a {color: #0000DD; text-decoration: none;}');
buffer.write('a:link.error {background-color: #FFEEEE;}');
buffer.write('a:visited.error {background-color: #FFEEEE;}');
buffer.write('a:hover.error {background-color: #FFEEEE;}');
buffer.write('a:active.error {background-color: #FFEEEE;}');
buffer.write(
'h3 {background-color: #DDDDDD; margin-top: 0em; margin-bottom: 0em;}');
buffer.write('p {margin-top: 0.5em; margin-bottom: 0.5em;}');
buffer.write(
'p.commentary {margin-top: 1em; margin-bottom: 1em; margin-left: 2em; font-style: italic;}');
// response.write('span.error {text-decoration-line: underline; text-decoration-color: red; text-decoration-style: wavy;}');
buffer.write(
'table.column {border: 0px solid black; width: 100%; table-layout: fixed;}');
buffer.write('td.column {vertical-align: top; width: 50%;}');
buffer.write('td.right {text-align: right;}');
buffer.write('</style>');
buffer.write('</head>');
buffer.write('<body>');
buffer.write(
'<h2>$title <small><small>(as of $time on $date)</small></small></h2>');
if (subtitles != null && subtitles.isNotEmpty) {
buffer.write('<blockquote>');
bool first = true;
for (String subtitle in subtitles) {
if (first) {
first = false;
} else {
buffer.write('<br>');
}
buffer.write('<b>');
buffer.write(subtitle);
buffer.write('</b>');
}
buffer.write('</blockquote>');
}
try {
body(buffer);
} catch (exception, stackTrace) {
buffer.write('<h3>Exception while creating page</h3>');
_writeException(buffer, new CaughtException(exception, stackTrace));
}
buffer.write('</body>');
buffer.write('</html>');
}
/**
* 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(plugin.uniqueIdentifier);
buffer.write(' (');
buffer.write(plugin.runtimeType);
buffer.write(')<br>');
}
buffer.write('<h3>Plugin Status</h3><p>');
writePlugin(AnalysisEngine.instance.enginePlugin);
writePlugin(_server.serverPlugin);
for (Plugin plugin in _server.analysisServer.userDefinedPlugins) {
writePlugin(plugin);
}
buffer.write('<p>');
}
/**
* Write the recent output section (on the main status page) to the given
* [buffer] object.
*/
void _writeRecentOutput(StringBuffer buffer) {
buffer.write('<h3>Recent Output</h3>');
String output = HTML_ESCAPE.convert(_printBuffer.join('\n'));
if (output.isEmpty) {
buffer.write('<i>none</i>');
} else {
buffer.write('<pre>');
buffer.write(output);
buffer.write('</pre>');
}
}
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<Object> columns,
{bool header: false, List<String> classes}) {
buffer.write('<tr>');
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('<th$classAttribute>');
} else {
buffer.write('<td$classAttribute>');
}
buffer.write(columns[i]);
if (header) {
buffer.write('</th>');
} else {
buffer.write('</td>');
}
}
buffer.write('</tr>');
}
/**
* 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<ServerService> services = analysisServer.serverServices;
buffer.write('<h3>Server Domain</h3>');
_writeTwoColumns(buffer, (StringBuffer buffer) {
if (analysisServer == null) {
buffer.write('Status: <span style="color:red">Not running</span>');
return false;
}
buffer.write('<p>');
buffer.write('Status: Running<br>');
buffer.write('Instrumentation: ');
if (AnalysisEngine.instance.instrumentationService.isActive) {
buffer.write('<span style="color:red">Active</span>');
} else {
buffer.write('Inactive');
}
buffer.write('<br>');
buffer.write('Version: ');
buffer.write(AnalysisServer.VERSION);
buffer.write('<br>');
buffer.write('Process ID: ');
buffer.write(pid);
buffer.write('</p>');
buffer.write('<p><b>Performance Data</b></p>');
buffer.write('<p>');
buffer.write(makeLink(
COMMUNICATION_PERFORMANCE_PATH, {}, 'Communication performance'));
buffer.write('</p>');
buffer.write('<p>');
buffer.write(makeLink(DIAGNOSTIC_PATH, {}, 'General diagnostics'));
buffer.write('</p>');
}, (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('#', '<br>#');
if (trace.startsWith('<br>#')) {
trace = trace.substring(4);
}
buffer.write('<p>');
buffer.write(trace);
buffer.write('</p>');
}
}
/**
* 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<Enum> subscribedServices) {
if (subscribedServices.contains(service)) {
buffer.write('<code>+ </code>');
} else {
buffer.write('<code>- </code>');
}
buffer.write(service.name);
buffer.write('<br>');
}
/**
* 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<String> subscribedPaths) {
buffer.write('<p>');
buffer.write(service.name);
buffer.write('</p>');
if (subscribedPaths == null || subscribedPaths.isEmpty) {
buffer.write('none');
} else {
List<String> paths = subscribedPaths.toList();
paths.sort();
for (String path in paths) {
buffer.write('<p>');
buffer.write(path);
buffer.write('</p>');
}
}
}
/**
* 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<Enum> allServices,
Set<Enum> subscribedServices) {
buffer.write('<p><b>Subscriptions</b></p>');
buffer.write('<p>');
for (Enum service in allServices) {
_writeSubscriptionInList(buffer, service, subscribedServices);
}
buffer.write('</p>');
}
/**
* 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<Enum> allServices,
Map<Enum, Set<String>> subscribedServices) {
buffer.write('<p><b>Subscriptions</b></p>');
for (Enum service in allServices) {
_writeSubscriptionInMap(buffer, service, subscribedServices[service]);
}
}
/**
* Write the targeted results returned by iterating over the [results] to the
* given [buffer]. The list will have the given [title] written before it.
*/
void _writeTargetedResults(
StringBuffer buffer, String title, Iterable<TargetedResult> results) {
List<TargetedResult> sortedResults = results.toList();
sortedResults.sort((TargetedResult first, TargetedResult second) {
int nameOrder =
first.result.toString().compareTo(second.result.toString());
if (nameOrder != 0) {
return nameOrder;
}
return first.target.toString().compareTo(second.target.toString());
});
buffer.write('<p>');
buffer.write(title);
buffer.write('</p><blockquote>');
if (results.isEmpty) {
buffer.write('nothing');
} else {
for (TargetedResult result in sortedResults) {
buffer.write('<p>');
buffer.write(result.result.toString());
buffer.write(' of ');
buffer.write(result.target.toString());
buffer.write('<p>');
}
}
buffer.write('</blockquote>');
}
/**
* 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('<table class="column"><tr class="column"><td class="column">');
leftColumn(buffer);
buffer.write('</td><td class="column">');
rightColumn(buffer);
buffer.write('</td></tr></table>');
}
/**
* 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<String, String> linkParameters) {
if (value == null) {
buffer.write('<i>null</i>');
} else if (value is String) {
buffer.write('<pre>${HTML_ESCAPE.convert(value)}</pre>');
} else if (value is List) {
buffer.write('List containing ${value.length} entries');
buffer.write('<ul>');
for (var entry in value) {
_writeListItem(buffer, () {
_writeValueAsHtml(buffer, entry, linkParameters);
});
}
buffer.write('</ul>');
} else if (value is AstNode) {
String link =
makeLink(AST_PATH, linkParameters, value.runtimeType.toString());
buffer.write('<i>$link</i>');
} else if (value is Element) {
String link =
makeLink(ELEMENT_PATH, linkParameters, value.runtimeType.toString());
buffer.write('<i>$link</i>');
} else if (value is UsedLocalElements) {
buffer.write('<ul>');
_writeListItem(buffer, () {
HashSet<Element> elements = value.elements;
buffer.write('List "elements" containing ${elements.length} entries');
buffer.write('<ul>');
for (Element element in elements) {
_writeListItem(buffer, () {
String elementStr = HTML_ESCAPE.convert(element.toString());
buffer.write('<i>${element.runtimeType}</i> $elementStr');
});
}
buffer.write('</ul>');
});
_writeListItem(buffer, () {
_writeListOfStrings(buffer, 'members', value.members);
});
_writeListItem(buffer, () {
_writeListOfStrings(buffer, 'readMembers', value.readMembers);
});
buffer.write('</ul>');
} else {
buffer.write(HTML_ESCAPE.convert(value.toString()));
buffer.write(' <i>(${value.runtimeType.toString()})</i>');
}
}
/**
* 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<String, String> 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 '<a href="$href"$classAttribute>$innerHtml</a>';
}
}