mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 22:00:09 +00:00
[analysis_server] Record more time in LSP completions
+ add a simple page to render the timings in server diagnostics pages. Change-Id: Ie8537cd38166677967ebe461b91334259b8883ba Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243322 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
8566dbeb57
commit
b7facb36d2
|
@ -340,9 +340,12 @@ class CompletionHandler extends MessageHandler<CompletionParams, CompletionList>
|
|||
);
|
||||
});
|
||||
|
||||
final serverSuggestions = serverSuggestions2.map((serverSuggestion) {
|
||||
return serverSuggestion.build();
|
||||
}).toList();
|
||||
final serverSuggestions =
|
||||
performance.run('buildSuggestions', (performance) {
|
||||
return serverSuggestions2
|
||||
.map((serverSuggestion) => serverSuggestion.build())
|
||||
.toList();
|
||||
});
|
||||
|
||||
final insertLength = _computeInsertLength(
|
||||
offset,
|
||||
|
@ -361,44 +364,46 @@ class CompletionHandler extends MessageHandler<CompletionParams, CompletionList>
|
|||
? false
|
||||
: server.clientConfiguration.global.completeFunctionCalls;
|
||||
|
||||
final results = serverSuggestions.map(
|
||||
(item) {
|
||||
var itemReplacementOffset =
|
||||
item.replacementOffset ?? completionRequest.replacementOffset;
|
||||
var itemReplacementLength =
|
||||
item.replacementLength ?? completionRequest.replacementLength;
|
||||
var itemInsertLength = insertLength;
|
||||
final results = performance.run('mapSuggestions', (performance) {
|
||||
return serverSuggestions.map(
|
||||
(item) {
|
||||
var itemReplacementOffset =
|
||||
item.replacementOffset ?? completionRequest.replacementOffset;
|
||||
var itemReplacementLength =
|
||||
item.replacementLength ?? completionRequest.replacementLength;
|
||||
var itemInsertLength = insertLength;
|
||||
|
||||
// Recompute the insert length if it may be affected by the above.
|
||||
if (item.replacementOffset != null ||
|
||||
item.replacementLength != null) {
|
||||
itemInsertLength = _computeInsertLength(
|
||||
offset, itemReplacementOffset, itemInsertLength);
|
||||
}
|
||||
// Recompute the insert length if it may be affected by the above.
|
||||
if (item.replacementOffset != null ||
|
||||
item.replacementLength != null) {
|
||||
itemInsertLength = _computeInsertLength(
|
||||
offset, itemReplacementOffset, itemInsertLength);
|
||||
}
|
||||
|
||||
// Convert to LSP ranges using the LineInfo.
|
||||
Range? replacementRange = toRange(
|
||||
unit.lineInfo, itemReplacementOffset, itemReplacementLength);
|
||||
Range? insertionRange =
|
||||
toRange(unit.lineInfo, itemReplacementOffset, itemInsertLength);
|
||||
// Convert to LSP ranges using the LineInfo.
|
||||
Range? replacementRange = toRange(
|
||||
unit.lineInfo, itemReplacementOffset, itemReplacementLength);
|
||||
Range? insertionRange =
|
||||
toRange(unit.lineInfo, itemReplacementOffset, itemInsertLength);
|
||||
|
||||
return toCompletionItem(
|
||||
capabilities,
|
||||
unit.lineInfo,
|
||||
item,
|
||||
replacementRange: replacementRange,
|
||||
insertionRange: insertionRange,
|
||||
// TODO(dantup): Move commit characters to the main response
|
||||
// and remove from each individual item (to reduce payload size)
|
||||
// once the following change ships (and the Dart VS Code
|
||||
// extension is updated to use it).
|
||||
// https://github.com/microsoft/vscode-languageserver-node/issues/673
|
||||
includeCommitCharacters:
|
||||
server.clientConfiguration.global.previewCommitCharacters,
|
||||
completeFunctionCalls: completeFunctionCalls,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
return toCompletionItem(
|
||||
capabilities,
|
||||
unit.lineInfo,
|
||||
item,
|
||||
replacementRange: replacementRange,
|
||||
insertionRange: insertionRange,
|
||||
// TODO(dantup): Move commit characters to the main response
|
||||
// and remove from each individual item (to reduce payload size)
|
||||
// once the following change ships (and the Dart VS Code
|
||||
// extension is updated to use it).
|
||||
// https://github.com/microsoft/vscode-languageserver-node/issues/673
|
||||
includeCommitCharacters:
|
||||
server.clientConfiguration.global.previewCommitCharacters,
|
||||
completeFunctionCalls: completeFunctionCalls,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
});
|
||||
|
||||
// Now compute items in suggestion sets.
|
||||
var includedSuggestionSets = <IncludedSuggestionSet>[];
|
||||
|
@ -407,97 +412,107 @@ class CompletionHandler extends MessageHandler<CompletionParams, CompletionList>
|
|||
includedElementKinds != null &&
|
||||
includedElementNames != null &&
|
||||
includedSuggestionRelevanceTags != null) {
|
||||
computeIncludedSetList(
|
||||
declarationsTracker,
|
||||
completionRequest,
|
||||
includedSuggestionSets,
|
||||
includedElementNames,
|
||||
);
|
||||
performance.run('computeIncludedSetList', (performance) {
|
||||
// Checked in `if` above.
|
||||
includedElementNames!;
|
||||
|
||||
computeIncludedSetList(
|
||||
declarationsTracker,
|
||||
completionRequest,
|
||||
includedSuggestionSets,
|
||||
includedElementNames,
|
||||
);
|
||||
});
|
||||
|
||||
// Build a fast lookup for imported symbols so that we can filter out
|
||||
// duplicates.
|
||||
final alreadyImportedSymbols = _buildLookupOfImportedSymbols(unit);
|
||||
|
||||
for (var includedSet in includedSuggestionSets) {
|
||||
final library = declarationsTracker.getLibrary(includedSet.id);
|
||||
if (library == null) {
|
||||
break;
|
||||
performance.run('addIncludedSuggestionSets', (performance) {
|
||||
// Checked in `if` above.
|
||||
includedSuggestionRelevanceTags!;
|
||||
|
||||
for (var includedSet in includedSuggestionSets) {
|
||||
final library = declarationsTracker.getLibrary(includedSet.id);
|
||||
if (library == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Make a fast lookup for tag relevance.
|
||||
final tagBoosts = <String, int>{};
|
||||
for (var t in includedSuggestionRelevanceTags) {
|
||||
tagBoosts[t.tag] = t.relevanceBoost;
|
||||
}
|
||||
|
||||
// Only specific types of child declarations should be included.
|
||||
// This list matches what's in _protocolAvailableSuggestion in
|
||||
// the DAS implementation.
|
||||
bool shouldIncludeChild(Declaration child) =>
|
||||
child.kind == DeclarationKind.CONSTRUCTOR ||
|
||||
child.kind == DeclarationKind.ENUM_CONSTANT ||
|
||||
(child.kind == DeclarationKind.GETTER && child.isStatic) ||
|
||||
(child.kind == DeclarationKind.FIELD && child.isStatic);
|
||||
|
||||
// Collect declarations and their children.
|
||||
final allDeclarations = library.declarations
|
||||
.followedBy(library.declarations
|
||||
.expand((decl) => decl.children.where(shouldIncludeChild)))
|
||||
.toList();
|
||||
|
||||
final setResults = allDeclarations
|
||||
// Filter to only the kinds we should return.
|
||||
.where((item) => includedElementKinds!
|
||||
.contains(protocolElementKind(item.kind)))
|
||||
.where((item) {
|
||||
// Check existing imports to ensure we don't already import
|
||||
// this element (this exact element from its declaring
|
||||
// library, not just something with the same name). If we do
|
||||
// we'll want to skip it.
|
||||
final declaringUri =
|
||||
item.parent?.locationLibraryUri ?? item.locationLibraryUri!;
|
||||
|
||||
// For enums and named constructors, only the parent enum/class is in
|
||||
// the list of imported symbols so we use the parents name.
|
||||
final nameKey = item.kind == DeclarationKind.ENUM_CONSTANT ||
|
||||
item.kind == DeclarationKind.CONSTRUCTOR
|
||||
? item.parent!.name
|
||||
: item.name;
|
||||
final key = _createImportedSymbolKey(nameKey, declaringUri);
|
||||
final importingUris = alreadyImportedSymbols[key];
|
||||
|
||||
// Keep it only if:
|
||||
// - no existing imports include it
|
||||
// (in which case all libraries will be offered as
|
||||
// auto-imports)
|
||||
// - this is the first imported URI that includes it
|
||||
// (we don't want to repeat it for each imported library that
|
||||
// includes it)
|
||||
return importingUris == null ||
|
||||
importingUris.first == '${library.uri}';
|
||||
}).map((item) => declarationToCompletionItem(
|
||||
capabilities,
|
||||
unit.path,
|
||||
offset,
|
||||
includedSet,
|
||||
library,
|
||||
tagBoosts,
|
||||
unit.lineInfo,
|
||||
item,
|
||||
completionRequest.replacementOffset,
|
||||
insertLength,
|
||||
completionRequest.replacementLength,
|
||||
// TODO(dantup): Move commit characters to the main response
|
||||
// and remove from each individual item (to reduce payload size)
|
||||
// once the following change ships (and the Dart VS Code
|
||||
// extension is updated to use it).
|
||||
// https://github.com/microsoft/vscode-languageserver-node/issues/673
|
||||
includeCommitCharacters: server
|
||||
.clientConfiguration.global.previewCommitCharacters,
|
||||
completeFunctionCalls: completeFunctionCalls,
|
||||
));
|
||||
results.addAll(setResults);
|
||||
}
|
||||
|
||||
// Make a fast lookup for tag relevance.
|
||||
final tagBoosts = <String, int>{};
|
||||
for (var t in includedSuggestionRelevanceTags) {
|
||||
tagBoosts[t.tag] = t.relevanceBoost;
|
||||
}
|
||||
|
||||
// Only specific types of child declarations should be included.
|
||||
// This list matches what's in _protocolAvailableSuggestion in
|
||||
// the DAS implementation.
|
||||
bool shouldIncludeChild(Declaration child) =>
|
||||
child.kind == DeclarationKind.CONSTRUCTOR ||
|
||||
child.kind == DeclarationKind.ENUM_CONSTANT ||
|
||||
(child.kind == DeclarationKind.GETTER && child.isStatic) ||
|
||||
(child.kind == DeclarationKind.FIELD && child.isStatic);
|
||||
|
||||
// Collect declarations and their children.
|
||||
final allDeclarations = library.declarations
|
||||
.followedBy(library.declarations
|
||||
.expand((decl) => decl.children.where(shouldIncludeChild)))
|
||||
.toList();
|
||||
|
||||
final setResults = allDeclarations
|
||||
// Filter to only the kinds we should return.
|
||||
.where((item) => includedElementKinds!
|
||||
.contains(protocolElementKind(item.kind)))
|
||||
.where((item) {
|
||||
// Check existing imports to ensure we don't already import
|
||||
// this element (this exact element from its declaring
|
||||
// library, not just something with the same name). If we do
|
||||
// we'll want to skip it.
|
||||
final declaringUri =
|
||||
item.parent?.locationLibraryUri ?? item.locationLibraryUri!;
|
||||
|
||||
// For enums and named constructors, only the parent enum/class is in
|
||||
// the list of imported symbols so we use the parents name.
|
||||
final nameKey = item.kind == DeclarationKind.ENUM_CONSTANT ||
|
||||
item.kind == DeclarationKind.CONSTRUCTOR
|
||||
? item.parent!.name
|
||||
: item.name;
|
||||
final key = _createImportedSymbolKey(nameKey, declaringUri);
|
||||
final importingUris = alreadyImportedSymbols[key];
|
||||
|
||||
// Keep it only if:
|
||||
// - no existing imports include it
|
||||
// (in which case all libraries will be offered as
|
||||
// auto-imports)
|
||||
// - this is the first imported URI that includes it
|
||||
// (we don't want to repeat it for each imported library that
|
||||
// includes it)
|
||||
return importingUris == null ||
|
||||
importingUris.first == '${library.uri}';
|
||||
}).map((item) => declarationToCompletionItem(
|
||||
capabilities,
|
||||
unit.path,
|
||||
offset,
|
||||
includedSet,
|
||||
library,
|
||||
tagBoosts,
|
||||
unit.lineInfo,
|
||||
item,
|
||||
completionRequest.replacementOffset,
|
||||
insertLength,
|
||||
completionRequest.replacementLength,
|
||||
// TODO(dantup): Move commit characters to the main response
|
||||
// and remove from each individual item (to reduce payload size)
|
||||
// once the following change ships (and the Dart VS Code
|
||||
// extension is updated to use it).
|
||||
// https://github.com/microsoft/vscode-languageserver-node/issues/673
|
||||
includeCommitCharacters: server
|
||||
.clientConfiguration.global.previewCommitCharacters,
|
||||
completeFunctionCalls: completeFunctionCalls,
|
||||
));
|
||||
results.addAll(setResults);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add in any snippets.
|
||||
|
@ -510,23 +525,27 @@ class CompletionHandler extends MessageHandler<CompletionParams, CompletionList>
|
|||
if (capabilities.completionSnippets &&
|
||||
snippetsEnabled &&
|
||||
isEditableFile) {
|
||||
results.addAll(await _getDartSnippetItems(
|
||||
clientCapabilities: capabilities,
|
||||
unit: unit,
|
||||
offset: offset,
|
||||
lineInfo: unit.lineInfo,
|
||||
));
|
||||
await performance.runAsync('addSnippets', (performance) async {
|
||||
results.addAll(await _getDartSnippetItems(
|
||||
clientCapabilities: capabilities,
|
||||
unit: unit,
|
||||
offset: offset,
|
||||
lineInfo: unit.lineInfo,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
// Perform fuzzy matching based on the identifier in front of the caret to
|
||||
// reduce the size of the payload.
|
||||
final fuzzyPattern = completionRequest.targetPrefix;
|
||||
final fuzzyMatcher =
|
||||
FuzzyMatcher(fuzzyPattern, matchStyle: MatchStyle.TEXT);
|
||||
final matchingResults = performance.run('fuzzyFilter', (performance) {
|
||||
final fuzzyPattern = completionRequest.targetPrefix;
|
||||
final fuzzyMatcher =
|
||||
FuzzyMatcher(fuzzyPattern, matchStyle: MatchStyle.TEXT);
|
||||
|
||||
final matchingResults = results
|
||||
.where((e) => fuzzyMatcher.score(e.filterText ?? e.label) > 0)
|
||||
.toList();
|
||||
return results
|
||||
.where((e) => fuzzyMatcher.score(e.filterText ?? e.label) > 0)
|
||||
.toList();
|
||||
});
|
||||
|
||||
// Transmitted count will be set after combining with plugins.
|
||||
completionPerformance.computedSuggestionCount = matchingResults.length;
|
||||
|
|
|
@ -35,6 +35,8 @@ String _computeCompletionSnippet(String contents, int offset) {
|
|||
|
||||
/// Overall performance of a code completion operation.
|
||||
class CompletionPerformance {
|
||||
static var _nextId = 1;
|
||||
final int id;
|
||||
final OperationPerformance operation;
|
||||
final String path;
|
||||
final String snippet;
|
||||
|
@ -48,7 +50,8 @@ class CompletionPerformance {
|
|||
this.requestLatency,
|
||||
required String content,
|
||||
required int offset,
|
||||
}) : snippet = _computeCompletionSnippet(content, offset);
|
||||
}) : id = _nextId++,
|
||||
snippet = _computeCompletionSnippet(content, offset);
|
||||
|
||||
String get computedSuggestionCountStr {
|
||||
if (computedSuggestionCount == null) return '';
|
||||
|
|
|
@ -138,6 +138,10 @@ td.pre {
|
|||
.footer strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.subtle {
|
||||
color: #333;
|
||||
}
|
||||
''';
|
||||
|
||||
String get _sdkVersion {
|
||||
|
@ -220,7 +224,9 @@ abstract class AbstractCompletionPage extends DiagnosticPageWithNav {
|
|||
for (var completion in completions) {
|
||||
var shortName = pathContext.basename(completion.path);
|
||||
buf.writeln('<tr>'
|
||||
'<td class="pre right">${_formatTiming(completion)}</td>'
|
||||
'<td class="pre right"><a href="/timing?id=${completion.id}">'
|
||||
'${_formatTiming(completion)}'
|
||||
'</a></td>'
|
||||
'<td class="right">${completion.computedSuggestionCountStr}</td>'
|
||||
'<td class="right">${completion.transmittedSuggestionCountStr}</td>'
|
||||
'<td>${escape(shortName)}</td>'
|
||||
|
@ -237,7 +243,7 @@ abstract class AbstractCompletionPage extends DiagnosticPageWithNav {
|
|||
var latency = completion.requestLatency;
|
||||
if (latency != null) {
|
||||
buffer
|
||||
..write(' <small title="client-to-server latency">(+ ')
|
||||
..write(' <small class="subtle" title="client-to-server latency">(+ ')
|
||||
..write(printMilliseconds(latency))
|
||||
..write(')</small>');
|
||||
}
|
||||
|
@ -246,6 +252,37 @@ abstract class AbstractCompletionPage extends DiagnosticPageWithNav {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class AbstractCompletionTimingPage extends DiagnosticPageWithNav {
|
||||
AbstractCompletionTimingPage(DiagnosticsSite site)
|
||||
: super(site, 'timing', 'Timing', description: 'Timing statistics.');
|
||||
|
||||
path.Context get pathContext;
|
||||
|
||||
List<CompletionPerformance> get performanceItems;
|
||||
|
||||
@override
|
||||
Future generateContent(Map<String, String> params) async {
|
||||
var id = int.parse(params['id'] ?? '');
|
||||
var completionInfo =
|
||||
performanceItems.firstWhereOrNull((info) => info.id == id);
|
||||
|
||||
if (completionInfo == null) {
|
||||
blankslate('Unable to find completion data for $id. '
|
||||
'Perhaps newer completion requests have pushed it out of the buffer?');
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = StringBuffer();
|
||||
completionInfo.operation.write(buffer: buffer);
|
||||
pre(() {
|
||||
buf.write('<code>');
|
||||
buf.write(escape('$buffer'));
|
||||
buf.writeln('</code>');
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
class AstPage extends DiagnosticPageWithNav {
|
||||
String? _description;
|
||||
|
||||
|
@ -390,6 +427,20 @@ class CompletionPage extends AbstractCompletionPage {
|
|||
server.completionState.performanceList.items.toList();
|
||||
}
|
||||
|
||||
class CompletionTimingPage extends AbstractCompletionTimingPage {
|
||||
@override
|
||||
AnalysisServer server;
|
||||
|
||||
CompletionTimingPage(super.site, this.server);
|
||||
|
||||
@override
|
||||
path.Context get pathContext => server.resourceProvider.pathContext;
|
||||
|
||||
@override
|
||||
List<CompletionPerformance> get performanceItems =>
|
||||
server.completionState.performanceList.items.toList();
|
||||
}
|
||||
|
||||
class ContentsPage extends DiagnosticPageWithNav {
|
||||
String? _description;
|
||||
|
||||
|
@ -792,9 +843,11 @@ class DiagnosticsSite extends Site implements AbstractGetHandler {
|
|||
}
|
||||
if (server is AnalysisServer) {
|
||||
pages.add(CompletionPage(this, server));
|
||||
pages.add(CompletionTimingPage(this, server));
|
||||
pages.add(SubscriptionsPage(this, server));
|
||||
} else if (server is LspAnalysisServer) {
|
||||
pages.add(LspCompletionPage(this, server));
|
||||
pages.add(LspCompletionTimingPage(this, server));
|
||||
pages.add(LspCapabilitiesPage(this, server));
|
||||
}
|
||||
|
||||
|
@ -1068,6 +1121,20 @@ class LspCompletionPage extends AbstractCompletionPage {
|
|||
server.performanceStats.completion.items.toList();
|
||||
}
|
||||
|
||||
class LspCompletionTimingPage extends AbstractCompletionTimingPage {
|
||||
@override
|
||||
LspAnalysisServer server;
|
||||
|
||||
LspCompletionTimingPage(super.site, this.server);
|
||||
|
||||
@override
|
||||
path.Context get pathContext => server.resourceProvider.pathContext;
|
||||
|
||||
@override
|
||||
List<CompletionPerformance> get performanceItems =>
|
||||
server.performanceStats.completion.items.toList();
|
||||
}
|
||||
|
||||
class MemoryAndCpuPage extends DiagnosticPageWithNav {
|
||||
final ProcessProfiler profiler;
|
||||
|
||||
|
|
Loading…
Reference in a new issue