diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDiagnosticsBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDiagnosticsBenchmark.cs index b84576aa389..ad7faea3277 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDiagnosticsBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDiagnosticsBenchmark.cs @@ -48,7 +48,7 @@ public async Task SetupAsync() DocumentPullDiagnosticsEndpoint = new DocumentPullDiagnosticsEndpoint( languageServerFeatureOptions: languageServer.GetRequiredService(), translateDiagnosticsService: languageServer.GetRequiredService(), - languageServer: new ClientNotifierService(BuildDiagnostics(N))); + languageServer: new ClientNotifierService(BuildDiagnostics(N)), telemetryReporter: null); var projectRoot = Path.Combine(RepoRoot, "src", "Razor", "test", "testapps", "ComponentApp"); var projectFilePath = Path.Combine(projectRoot, "ComponentApp.csproj"); _filePath = Path.Combine(projectRoot, "Components", "Pages", $"Generated.razor"); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.OmniSharp/Project/OmniSharpTelemetryReporter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.OmniSharp/Project/OmniSharpTelemetryReporter.cs index fed628974c4..451b94333bb 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.OmniSharp/Project/OmniSharpTelemetryReporter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.OmniSharp/Project/OmniSharpTelemetryReporter.cs @@ -30,6 +30,11 @@ public IDisposable BeginBlock(string name, Severity severity, ImmutableDictionar return NullScope.Instance; } + public IDisposable TrackLspRequest(string name, string lspMethodName, string lspServerName, Guid correlationId) + { + return NullScope.Instance; + } + private class NullScope : IDisposable { public static NullScope Instance { get; } = new NullScope(); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs index 9bfcb9200b4..12c3ade2137 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/LanguageServerConstants.cs @@ -19,6 +19,8 @@ public static class LanguageServerConstants public const string RazorProximityExpressionsEndpoint = "razor/proximityExpressions"; + public const string RazorLanguageServerName = "Razor Language Server Client"; + public const string RazorMonitorProjectConfigurationFilePathEndpoint = "razor/monitorProjectConfigurationFilePath"; public const string RazorMapToDocumentRangesEndpoint = "razor/mapToDocumentRanges"; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs index 2bad9193c14..b0921b01448 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs @@ -1,7 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using Microsoft.VisualStudio.LanguageServer.Protocol; +using Range = Microsoft.VisualStudio.LanguageServer.Protocol.Range; // A file for delegated record types to be put. Each individually // should be a plain record. If more logic is needed than record @@ -10,7 +12,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Protocol; internal record DelegatedDiagnosticParams( - VersionedTextDocumentIdentifier HostDocument); + VersionedTextDocumentIdentifier HostDocument, + Guid CorrelationId); internal record DelegatedPositionParams( VersionedTextDocumentIdentifier HostDocument, diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs index 410dcb13b8d..cebc99262ab 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; using Microsoft.AspNetCore.Razor.LanguageServer.Protocol; using Microsoft.AspNetCore.Razor.PooledObjects; +using Microsoft.AspNetCore.Razor.Telemetry; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CommonLanguageServerProtocol.Framework; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -22,15 +23,18 @@ internal class DocumentPullDiagnosticsEndpoint : IRazorRequestHandler false; @@ -57,11 +61,13 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno return default; } + var correlationId = Guid.NewGuid(); + using var __ = _telemetryReporter?.TrackLspRequest("diagnostics", VSInternalMethods.DocumentPullDiagnosticName, LanguageServerConstants.RazorLanguageServerName, correlationId); var documentContext = context.GetRequiredDocumentContext(); var razorDiagnostics = await GetRazorDiagnosticsAsync(documentContext, cancellationToken).ConfigureAwait(false); - var (csharpDiagnostics, htmlDiagnostics) = await GetHtmlCSharpDiagnosticsAsync(documentContext, cancellationToken).ConfigureAwait(false); + var (csharpDiagnostics, htmlDiagnostics) = await GetHtmlCSharpDiagnosticsAsync(documentContext, correlationId, cancellationToken).ConfigureAwait(false); using var _ = ListPool.GetPooledObject(out var allDiagnostics); allDiagnostics.SetCapacityIfLarger( @@ -131,9 +137,9 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno return razorDiagnostics; } - private async Task<(VSInternalDiagnosticReport[]? CSharpDiagnostics, VSInternalDiagnosticReport[]? HtmlDiagnostics)> GetHtmlCSharpDiagnosticsAsync(VersionedDocumentContext documentContext, CancellationToken cancellationToken) + private async Task<(VSInternalDiagnosticReport[]? CSharpDiagnostics, VSInternalDiagnosticReport[]? HtmlDiagnostics)> GetHtmlCSharpDiagnosticsAsync(VersionedDocumentContext documentContext, Guid correlationId, CancellationToken cancellationToken) { - var delegatedParams = new DelegatedDiagnosticParams(documentContext.Identifier); + var delegatedParams = new DelegatedDiagnosticParams(documentContext.Identifier, correlationId); var delegatedResponse = await _languageServer.SendRequestAsync( RazorLanguageServerCustomMessageTargets.RazorPullDiagnosticEndpointName, delegatedParams, diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Telemetry/ITelemetryReporter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Telemetry/ITelemetryReporter.cs index 238201d42b4..a8fed86919c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Telemetry/ITelemetryReporter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Telemetry/ITelemetryReporter.cs @@ -10,6 +10,7 @@ public interface ITelemetryReporter { IDisposable BeginBlock(string name, Severity severity); IDisposable BeginBlock(string name, Severity severity, ImmutableDictionary values); + IDisposable TrackLspRequest(string name, string lspMethodName, string lspServerName, Guid correlationId); void ReportEvent(string name, Severity severity); void ReportEvent(string name, Severity severity, ImmutableDictionary values); void ReportFault(Exception exception, string? message, params object?[] @params); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Telemetry/NoOpTelemetryReporter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Telemetry/NoOpTelemetryReporter.cs index 02446bfa469..0684227be70 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Telemetry/NoOpTelemetryReporter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Telemetry/NoOpTelemetryReporter.cs @@ -36,6 +36,11 @@ public IDisposable BeginBlock(string name, Severity severity, ImmutableDictionar return NullScope.Instance; } + public IDisposable TrackLspRequest(string name, string lspMethodName, string lspServerName, Guid correlationId) + { + return NullScope.Instance; + } + private class NullScope : IDisposable { public static NullScope Instance { get; } = new NullScope(); diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/TelemetryReporter.cs b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/TelemetryReporter.cs index b5640410722..67e825d7759 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/TelemetryReporter.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/TelemetryReporter.cs @@ -221,6 +221,28 @@ public IDisposable BeginBlock(string name, Severity severity, ImmutableDictionar return new TelemetryScope(this, name, severity, values.ToImmutableDictionary((tuple) => tuple.Key, (tuple) => (object?)tuple.Value)); } + public IDisposable TrackLspRequest(string name, string lspMethodName, string languageServerName, Guid correlationId) + { + if (correlationId == Guid.Empty) + { + return NullTelemetryScope.Instance; + } + + return BeginBlock(name, Severity.Normal, ImmutableDictionary.CreateRange(new KeyValuePair[] + { + new("eventscope.method", lspMethodName), + new("eventscope.languageservername", languageServerName), + new("eventscope.correlationid", correlationId), + })); + } + + private class NullTelemetryScope : IDisposable + { + public static NullTelemetryScope Instance { get; } = new NullTelemetryScope(); + private NullTelemetryScope() { } + public void Dispose() { } + } + private class TelemetryScope : IDisposable { private readonly ITelemetryReporter _telemetryReporter; diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs index 290eaf1da55..62148252017 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/DefaultRazorLanguageServerCustomMessageTarget.cs @@ -46,6 +46,7 @@ internal class DefaultRazorLanguageServerCustomMessageTarget : RazorLanguageServ private readonly TrackingLSPDocumentManager _documentManager; private readonly JoinableTaskFactory _joinableTaskFactory; private readonly LSPRequestInvoker _requestInvoker; + private readonly ITelemetryReporter _telemetryReporter; private readonly FormattingOptionsProvider _formattingOptionsProvider; private readonly IClientSettingsManager _editorSettingsManager; private readonly LSPDocumentSynchronizer _documentSynchronizer; @@ -106,10 +107,11 @@ public DefaultRazorLanguageServerCustomMessageTarget( _joinableTaskFactory = joinableTaskContext.Factory; - _requestInvoker = new TelemetryReportingLSPRequestInvoker(requestInvoker, telemetryReporter); + _requestInvoker = requestInvoker; _formattingOptionsProvider = formattingOptionsProvider; _editorSettingsManager = editorSettingsManager; _documentSynchronizer = documentSynchronizer; + _telemetryReporter = telemetryReporter; _outputWindowLogger = outputWindowLogger; } @@ -1117,8 +1119,8 @@ public override Task ImplementationAsync(DelegatedPosition public override async Task DiagnosticsAsync(DelegatedDiagnosticParams request, CancellationToken cancellationToken) { - var csharpTask = Task.Run(() => GetVirtualDocumentPullDiagnosticsAsync(request.HostDocument, RazorLSPConstants.RazorCSharpLanguageServerName, cancellationToken), cancellationToken); - var htmlTask = Task.Run(() => GetVirtualDocumentPullDiagnosticsAsync(request.HostDocument, RazorLSPConstants.HtmlLanguageServerName, cancellationToken), cancellationToken); + var csharpTask = Task.Run(() => GetVirtualDocumentPullDiagnosticsAsync(request.HostDocument, request.CorrelationId, RazorLSPConstants.RazorCSharpLanguageServerName, cancellationToken), cancellationToken); + var htmlTask = Task.Run(() => GetVirtualDocumentPullDiagnosticsAsync(request.HostDocument, request.CorrelationId, RazorLSPConstants.HtmlLanguageServerName, cancellationToken), cancellationToken); try { @@ -1143,7 +1145,7 @@ public override Task ImplementationAsync(DelegatedPosition return new RazorPullDiagnosticResponse(csharpDiagnostics, htmlDiagnostics); } - private async Task GetVirtualDocumentPullDiagnosticsAsync(VersionedTextDocumentIdentifier hostDocument, string delegatedLanguageServerName, CancellationToken cancellationToken) + private async Task GetVirtualDocumentPullDiagnosticsAsync(VersionedTextDocumentIdentifier hostDocument, Guid correlationId, string delegatedLanguageServerName, CancellationToken cancellationToken) where TVirtualDocumentSnapshot : VirtualDocumentSnapshot { var (synchronized, virtualDocument) = await _documentSynchronizer.TrySynchronizeVirtualDocumentAsync( @@ -1162,6 +1164,8 @@ public override Task ImplementationAsync(DelegatedPosition Uri = virtualDocument.Uri, }, }; + + using var _ = _telemetryReporter.TrackLspRequest(nameof(_requestInvoker.ReinvokeRequestOnServerAsync), VSInternalMethods.DocumentPullDiagnosticName, delegatedLanguageServerName, correlationId); var response = await _requestInvoker.ReinvokeRequestOnServerAsync( virtualDocument.Snapshot.TextBuffer, VSInternalMethods.DocumentPullDiagnosticName, diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/TelemetryReportingLSPRequestInvoker.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/TelemetryReportingLSPRequestInvoker.cs deleted file mode 100644 index 9d9ec467cfa..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/TelemetryReportingLSPRequestInvoker.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.Text; -using Microsoft.AspNetCore.Razor.Telemetry; -using Microsoft.VisualStudio.LanguageServer.ContainedLanguage; -using Newtonsoft.Json.Linq; - -namespace Microsoft.VisualStudio.LanguageServerClient.Razor; - -internal class TelemetryReportingLSPRequestInvoker : LSPRequestInvoker -{ - private readonly LSPRequestInvoker _requestInvoker; - private readonly ITelemetryReporter _telemetryReporter; - - public TelemetryReportingLSPRequestInvoker(LSPRequestInvoker requestInvoker, ITelemetryReporter telemetryReporter) - { - _requestInvoker = requestInvoker; - _telemetryReporter = telemetryReporter; - } - - public override Task>> ReinvokeRequestOnMultipleServersAsync(string method, string contentType, TIn parameters, CancellationToken cancellationToken) - { - return _requestInvoker.ReinvokeRequestOnMultipleServersAsync(method, contentType, parameters, cancellationToken); - } - - public override Task>> ReinvokeRequestOnMultipleServersAsync(string method, string contentType, Func capabilitiesFilter, TIn parameters, CancellationToken cancellationToken) - { - return _requestInvoker.ReinvokeRequestOnMultipleServersAsync(method, contentType, capabilitiesFilter, parameters, cancellationToken); - } - - public override IAsyncEnumerable> ReinvokeRequestOnMultipleServersAsync(ITextBuffer textBuffer, string method, TIn parameters, CancellationToken cancellationToken) - { - return _requestInvoker.ReinvokeRequestOnMultipleServersAsync(textBuffer, method, parameters, cancellationToken); - } - - public override IAsyncEnumerable> ReinvokeRequestOnMultipleServersAsync(ITextBuffer textBuffer, string method, Func capabilitiesFilter, TIn parameters, CancellationToken cancellationToken) - { - return _requestInvoker.ReinvokeRequestOnMultipleServersAsync(textBuffer, method, capabilitiesFilter, parameters, cancellationToken); - } - - public override Task> ReinvokeRequestOnServerAsync(string method, string languageServerName, TIn parameters, CancellationToken cancellationToken) - { - using (Track(nameof(ReinvokeRequestOnServerAsync), method, languageServerName)) - { - return _requestInvoker.ReinvokeRequestOnServerAsync(method, languageServerName, parameters, cancellationToken); - } - } - - public override Task> ReinvokeRequestOnServerAsync(string method, string languageServerName, Func capabilitiesFilter, TIn parameters, CancellationToken cancellationToken) - { - using (Track(nameof(ReinvokeRequestOnServerAsync), method, languageServerName)) - { - return _requestInvoker.ReinvokeRequestOnServerAsync(method, languageServerName, capabilitiesFilter, parameters, cancellationToken); - } - } - - public override Task?> ReinvokeRequestOnServerAsync(ITextBuffer textBuffer, string method, string languageServerName, TIn parameters, CancellationToken cancellationToken) - { - using (Track(nameof(ReinvokeRequestOnServerAsync), method, languageServerName)) - { - return _requestInvoker.ReinvokeRequestOnServerAsync(textBuffer, method, languageServerName, parameters, cancellationToken); - } - } - - public override Task?> ReinvokeRequestOnServerAsync(ITextBuffer textBuffer, string method, string languageServerName, Func capabilitiesFilter, TIn parameters, CancellationToken cancellationToken) - { - using (Track(nameof(ReinvokeRequestOnServerAsync), method, languageServerName)) - { - return _requestInvoker.ReinvokeRequestOnServerAsync(textBuffer, method, languageServerName, capabilitiesFilter, parameters, cancellationToken); - } - } - - private IDisposable? Track(string name, string method, string languageServerName) - { - return _telemetryReporter.BeginBlock(name, Severity.Normal, ImmutableDictionary.CreateRange(new KeyValuePair[] - { - new("eventscope.method", method), - new("eventscope.languageservername", languageServerName), - })); - } -} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs index 864b7f72d22..7f23f1c4895 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs @@ -56,7 +56,7 @@ private async Task ValidateDiagnosticsAsync(string input) var requestContext = new RazorRequestContext(documentContext, Logger, null!); var translateDiagnosticsService = new RazorTranslateDiagnosticsService(DocumentMappingService, LoggerFactory); - var diagnosticsEndPoint = new DocumentPullDiagnosticsEndpoint(LanguageServerFeatureOptions, translateDiagnosticsService, LanguageServer); + var diagnosticsEndPoint = new DocumentPullDiagnosticsEndpoint(LanguageServerFeatureOptions, translateDiagnosticsService, LanguageServer, telemetryReporter: null); var diagnosticsRequest = new VSInternalDocumentDiagnosticsParams {