[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:
Danny Tuppeny 2022-05-02 15:50:50 +00:00 committed by Commit Bot
parent 8566dbeb57
commit b7facb36d2
3 changed files with 227 additions and 138 deletions

View file

@ -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;

View file

@ -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 '';

View file

@ -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;