From 6855984213865fcb6790dec025ca9637b0d1a976 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Thu, 3 Sep 2026 11:24:14 -0700 Subject: [PATCH 1/5] Make RoslynTelemetry instance-based Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../SmartRenameViewModel_Telemetry.cs | 4 +- .../Core/Remote/SolutionChecksumUpdater.cs | 2 +- .../SuggestedActions/EditorSuggestedAction.cs | 2 +- .../SuggestedActionsSource_Async.cs | 8 +- .../CodeFixes/Service/CodeFixService.cs | 14 +- .../CodeRefactoringService.cs | 4 +- ...icAnalyzerService_GetDiagnosticsForSpan.cs | 2 +- .../Service/DocumentAnalysisExecutor.cs | 4 +- .../LanguageServerRequestTelemetryTests.cs | 4 +- .../Telemetry/LanguageServerTelemetry.cs | 6 +- .../Telemetry/VSCodeRequestTelemetryLogger.cs | 4 +- .../Telemetry/RequestTelemetryLogger.cs | 12 +- .../AbstractWorkspaceTelemetryService.cs | 6 +- .../Services/ServiceHubServicesTests.cs | 2 +- .../ServiceHubServicesTests_Telemetry.cs | 4 +- ...xtViewWindowVerifierInProcessExtensions.cs | 2 +- .../OptionPages/PerformanceLoggersPage.cs | 4 +- .../PerfMargin/PerfMarginPanel.cs | 2 +- .../Core/Portable/CodeActions/CodeAction.cs | 2 +- .../Log/RoslynTelemetry.Workspaces.cs | 12 +- .../CoreTest/Log/RoslynTelemetryTests.cs | 174 +++++++++++++++--- .../RemoteAssetSynchronizationService.cs | 2 +- .../PerformanceTrackerService.cs | 4 +- .../RemoteProcessTelemetryService.cs | 4 +- .../Compiler/Core/Log/Logger.cs | 30 +-- .../Compiler/Core/Log/RoslynTelemetry.cs | 83 ++++++--- .../Core/Log/RoslynTelemetry_LogBlock.cs | 4 +- .../Core/Log/RoslynTelemetry_Metrics.cs | 50 ++--- 28 files changed, 306 insertions(+), 145 deletions(-) diff --git a/src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs b/src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs index 37f769b0999cf..2615a24523025 100644 --- a/src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs +++ b/src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs @@ -45,7 +45,7 @@ private void PostTelemetry(bool isCommit) if (_suggestionsPanelTelemetry is not null) { RoslynDebug.Assert(_suggestionsDropdownTelemetry is null); - RoslynTelemetry.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => + RoslynTelemetry.Current.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => { m[nameof(isCommit)] = isCommit; m["UseSuggestionsPanel"] = true; @@ -61,7 +61,7 @@ private void PostTelemetry(bool isCommit) else { RoslynDebug.Assert(_suggestionsDropdownTelemetry is not null); - RoslynTelemetry.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => + RoslynTelemetry.Current.Log(FunctionId.Copilot_Rename, KeyValueLogMessage.Create(m => { m[nameof(isCommit)] = isCommit; m["UseDropDown"] = true; diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 3b02fefbc3731..eb281e2270527 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -190,7 +190,7 @@ private async Task DispatchSynchronizeTextChangesAsync( // Update aggregated telemetry with success status of sending the synchronization data. var metricName = wasSynchronized.Value ? SynchronizeTextChangesStatusSucceededMetricName : SynchronizeTextChangesStatusFailedMetricName; - RoslynTelemetry.Count(FunctionId.ChecksumUpdater_SynchronizeTextChangesStatus, metricName, 1); + RoslynTelemetry.Current.Count(FunctionId.ChecksumUpdater_SynchronizeTextChangesStatus, metricName, 1); return; diff --git a/src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs b/src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs index 2c16d0f154d9b..c60d1fd45ac8b 100644 --- a/src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs +++ b/src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs @@ -101,7 +101,7 @@ private async Task InvokeAsync() { try { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.SuggestedAction_Application_Summary, $"Total"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.SuggestedAction_Application_Summary, $"Total"); using var token = SourceProvider.OperationListener.BeginAsyncOperation($"{nameof(EditorSuggestedAction)}.{nameof(Invoke)}"); using var context = SourceProvider.UIThreadOperationExecutor.BeginExecute( diff --git a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs index 4ac3c48dd1789..ecd7cc98e71a3 100644 --- a/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs +++ b/src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs @@ -104,14 +104,14 @@ await document.Project.Solution.Services.GetRequiredService> GetCodeFixesAsync() { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.SuggestedAction_Summary, $"Total.Pri{priority.GetPriorityInt()}.{nameof(GetCodeFixesAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.SuggestedAction_Summary, $"Total.Pri{priority.GetPriorityInt()}.{nameof(GetCodeFixesAsync)}"); if (owner._codeFixService == null || !supportsFeatureService.SupportsCodeFixes(target.SubjectBuffer) || @@ -235,7 +235,7 @@ await document.Project.Solution.Services.GetRequiredService> GetRefactoringsAsync() { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.SuggestedAction_Summary, $"Total.Pri{priority.GetPriorityInt()}.{nameof(GetRefactoringsAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.SuggestedAction_Summary, $"Total.Pri{priority.GetPriorityInt()}.{nameof(GetRefactoringsAsync)}"); if (!selection.HasValue) { diff --git a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs index 94c468afe876f..08995922eee8f 100644 --- a/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs @@ -95,11 +95,11 @@ private DiagnosticIdFilter GetShouldIncludeDiagnosticPredicate( public async Task GetMostSevereFixAsync( TextDocument document, TextSpan range, CodeActionRequestPriority? priority, CancellationToken cancellationToken) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}"); ImmutableArray allDiagnostics; - using (RoslynTelemetry.RecordBlockTime( + using (RoslynTelemetry.Current.RecordBlockTime( FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { var service = document.Project.Solution.Services.GetRequiredService(); @@ -173,7 +173,7 @@ public async IAsyncEnumerable StreamFixesAsync( CodeActionRequestPriority? priority, [EnumeratorCancellation] CancellationToken cancellationToken) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}"); // We only need to compute suppression/configuration fixes when request priority is // 'CodeActionPriorityRequest.Lowest' or no priority was provided at all (so all providers should run). @@ -191,7 +191,7 @@ public async IAsyncEnumerable StreamFixesAsync( // user-invoked diagnostic requests, for example, user invoked Ctrl + Dot operation for lightbulb. ImmutableArray diagnostics; - using (RoslynTelemetry.RecordBlockTime( + using (RoslynTelemetry.Current.RecordBlockTime( FunctionId.CodeFix_Summary, $"Pri{priority.GetPriorityInt()}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { var service = document.Project.Solution.Services.GetRequiredService(); @@ -286,7 +286,7 @@ private static SortedDictionary> ConvertToMap( { cancellationToken.ThrowIfCancellationRequested(); - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}"); ImmutableArray diagnostics; if (textSpan is null) @@ -295,7 +295,7 @@ private static SortedDictionary> ConvertToMap( textSpan = new TextSpan(0, text.Length); } - using (RoslynTelemetry.RecordBlockTime( + using (RoslynTelemetry.Current.RecordBlockTime( FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { var service = document.Project.Solution.Services.GetRequiredService(); @@ -534,7 +534,7 @@ private async IAsyncEnumerable StreamFixesAsync( m[TelemetryKeys.LanguageName] = document.Project.Language; }, (fixerName, document)); - using var _ = RoslynTelemetry.LogBlockTime(FunctionId.CodeFix_Delay, logMessage, CodeFixTelemetryDelay); + using var _ = RoslynTelemetry.Current.LogBlockTime(FunctionId.CodeFix_Delay, logMessage, CodeFixTelemetryDelay); var codeFixCollection = await TryGetFixesOrConfigurationsAsync( document, span, diagnostics, fixAllForInSpan, fixer, diff --git a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs index 5db5d1f9817d4..5d4c2f818a009 100644 --- a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs @@ -183,7 +183,7 @@ public async Task> GetRefactoringsAsync( CodeActionRequestPriority? priority, CancellationToken cancellationToken) { - using (RoslynTelemetry.RecordBlockTime(FunctionId.CodeRefactoring_Summary, $"Pri{priority.GetPriorityInt()}")) + using (RoslynTelemetry.Current.RecordBlockTime(FunctionId.CodeRefactoring_Summary, $"Pri{priority.GetPriorityInt()}")) using (Logger.LogBlock(FunctionId.Refactoring_CodeRefactoringService_GetRefactoringsAsync, cancellationToken)) { using var _ = PooledDictionary.GetInstance(out var providerToIndex); @@ -213,7 +213,7 @@ public async Task> GetRefactoringsAsync( }, (providerName, document)); using (RoslynEventSource.LogInformationalBlock(FunctionId.Refactoring_CodeRefactoringService_GetRefactoringsAsync, providerName, cancellationToken)) - using (RoslynTelemetry.LogBlockTime(FunctionId.CodeRefactoring_Delay, logMessage, CodeRefactoringTelemetryDelay)) + using (RoslynTelemetry.Current.LogBlockTime(FunctionId.CodeRefactoring_Delay, logMessage, CodeRefactoringTelemetryDelay)) { var refactoring = await @this.GetRefactoringFromProviderAsync( document, state, provider, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs index fa8deaf1224ee..4330ed9b6fd91 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs @@ -91,7 +91,7 @@ public async Task> GetDiagnosticsForSpanInProcess using var _2 = ArrayBuilder.GetInstance(out var semanticSpanBasedAnalyzers); using var _3 = ArrayBuilder.GetInstance(out var semanticDocumentBasedAnalyzers); - using var _4 = RoslynTelemetry.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"Pri{priority.GetPriorityInt()}"); + using var _4 = RoslynTelemetry.Current.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"Pri{priority.GetPriorityInt()}"); foreach (var analyzer in analyzers) { diff --git a/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs b/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs index 9d2a2c92195ea..8996351b2c725 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs @@ -216,7 +216,7 @@ async ValueTask> GetSyntaxDiagnosticsInProcessAsy if (_lazySyntaxDiagnostics == null) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"{nameof(GetSyntaxDiagnosticsInProcessAsync)}.{nameof(GetAnalysisResultInProcessAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"{nameof(GetSyntaxDiagnosticsInProcessAsync)}.{nameof(GetAnalysisResultInProcessAsync)}"); var analysisScope = AnalysisScope.WithAnalyzers(_compilationBasedAnalyzersInAnalysisScope); var syntaxDiagnostics = await GetAnalysisResultInProcessAsync(analysisScope).ConfigureAwait(false); @@ -252,7 +252,7 @@ async ValueTask> GetSemanticDiagnosticsInProcessA if (_lazySemanticDiagnostics == null) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"{nameof(GetSemanticDiagnosticsInProcessAsync)}.{nameof(GetAnalysisResultInProcessAsync)}"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.RequestDiagnostics_Summary, $"{nameof(GetSemanticDiagnosticsInProcessAsync)}.{nameof(GetAnalysisResultInProcessAsync)}"); var analysisScope = AnalysisScope.WithAnalyzers(_compilationBasedAnalyzersInAnalysisScope); var semanticDiagnostics = await GetAnalysisResultInProcessAsync(analysisScope).ConfigureAwait(false); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs index 62cc83231ae08..e4e6d01f589e2 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -36,7 +36,7 @@ public async Task RealRequestsProduceAggregatedTelemetry() { var poster = new RecordingPoster(); using var sink = VSMetricSink.TestAccessor.CreateSink(poster); - using var registration = RoslynTelemetry.AddMetricSink(sink); + using var registration = RoslynTelemetry.Current.AddMetricSink(sink); var server = await CreateLanguageServerAsync(); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs index ee6b51999b9fa..f4a4ee86e3fe4 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs @@ -91,8 +91,8 @@ public void InitializeSession(string telemetryLevel, string? sessionId, bool isD var metricSink = new VSMetricSink(session); _registrations = [ - RoslynTelemetry.AddEventSink(TelemetryEventSink.Create(session, logDelta: true)), - RoslynTelemetry.AddMetricSink(metricSink), + RoslynTelemetry.Current.AddEventSink(TelemetryEventSink.Create(session, logDelta: true)), + RoslynTelemetry.Current.AddMetricSink(metricSink), metricSink, ]; @@ -116,7 +116,7 @@ public void Dispose() if (_telemetrySession is { } session) { FeaturesSessionTelemetry.Report(); - RoslynTelemetry.Flush(); + RoslynTelemetry.Current.Flush(); foreach (var registration in _registrations) registration.Dispose(); diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs index 9e82a7bb38fb2..6919497428ff3 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -43,7 +43,7 @@ public static void ReportProjectLoadStarted() protected override void IncreaseFindDocumentCount(string workspaceCountMetricName) { var projectsLoaded = s_initialProjectLoadCompleted; - RoslynTelemetry.Count(FunctionId.LSP_FindDocumentInWorkspace, workspaceCountMetricName, 1, + RoslynTelemetry.Current.Count(FunctionId.LSP_FindDocumentInWorkspace, workspaceCountMetricName, 1, new("server", ServerTypeName), new("workspace", workspaceCountMetricName), new("projectsLoaded", projectsLoaded)); diff --git a/src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs b/src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs index 3b93f9f3bc960..335beb3531138 100644 --- a/src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs +++ b/src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs @@ -32,7 +32,7 @@ public void UpdateFindDocumentTelemetryData(bool success, string? workspaceKind) protected virtual void IncreaseFindDocumentCount(string workspaceCounterMetricName) { - RoslynTelemetry.Count(FunctionId.LSP_FindDocumentInWorkspace, workspaceCounterMetricName, 1, + RoslynTelemetry.Current.Count(FunctionId.LSP_FindDocumentInWorkspace, workspaceCounterMetricName, 1, new("server", ServerTypeName), new("workspace", workspaceCounterMetricName)); } @@ -40,7 +40,7 @@ protected virtual void IncreaseFindDocumentCount(string workspaceCounterMetricNa public void UpdateUsedForkedSolutionCounter(bool usedForkedSolution) { var metricName = usedForkedSolution ? "ForkedCount" : "NonForkedCount"; - RoslynTelemetry.Count(FunctionId.LSP_UsedForkedSolution, metricName, 1, + RoslynTelemetry.Current.Count(FunctionId.LSP_UsedForkedSolution, metricName, 1, new("server", ServerTypeName), new("usedForkedSolution", usedForkedSolution)); } @@ -53,10 +53,10 @@ public void UpdateTelemetryData( Result result) { // Store the request time metrics per LSP method. - RoslynTelemetry.Record(FunctionId.LSP_TimeInQueue, "TimeInQueue", (long)queuedDuration.TotalMilliseconds, + RoslynTelemetry.Current.Record(FunctionId.LSP_TimeInQueue, "TimeInQueue", (long)queuedDuration.TotalMilliseconds, new("server", ServerTypeName)); - RoslynTelemetry.Record(FunctionId.LSP_RequestDuration, "RequestDuration", (long)requestDuration.TotalMilliseconds, + RoslynTelemetry.Current.Record(FunctionId.LSP_RequestDuration, "RequestDuration", (long)requestDuration.TotalMilliseconds, new("server", ServerTypeName), new("method", methodName), new("language", language)); @@ -69,7 +69,7 @@ public void UpdateTelemetryData( _ => throw ExceptionUtilities.UnexpectedValue(result) }; - RoslynTelemetry.Count(FunctionId.LSP_RequestCounter, metricName, 1, + RoslynTelemetry.Current.Count(FunctionId.LSP_RequestCounter, metricName, 1, new("server", ServerTypeName), new("method", methodName), new("language", language)); @@ -79,7 +79,7 @@ public void Dispose() { // Ensure that telemetry logged for this server instance is flushed before potentially creating a new instance. // This is also called on disposal of the telemetry session, but will no-op if already flushed. - RoslynTelemetry.Flush(); + RoslynTelemetry.Current.Flush(); } internal enum Result diff --git a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs index 95cd1f86b6af9..6ef2cc53f625c 100644 --- a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs @@ -34,8 +34,8 @@ public void InitializeTelemetrySession(TelemetrySession telemetrySession, bool l var metricSink = new VSMetricSink(telemetrySession); _registrations = [ - .. CreateEventSinks(telemetrySession, logDelta).SelectAsArray(RoslynTelemetry.AddEventSink), - RoslynTelemetry.AddMetricSink(metricSink), + .. CreateEventSinks(telemetrySession, logDelta).SelectAsArray(RoslynTelemetry.Current.AddEventSink), + RoslynTelemetry.Current.AddMetricSink(metricSink), metricSink, ]; @@ -70,7 +70,7 @@ public void Dispose() { // Ensure any aggregate telemetry is flushed when the catalog is destroyed. // It is fine for this to be called multiple times - if telemetry has already been flushed this will no-op. - RoslynTelemetry.Flush(); + RoslynTelemetry.Current.Flush(); foreach (var registration in _registrations) registration.Dispose(); diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 1d433ee495a4b..4c732eaf2bdac 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -1949,7 +1949,7 @@ void M() Assert.Equal("CSharp.ConflictMarkerResolution.CSharpResolveConflictMarkerCodeFixProvider", result.CodeFixAnalysis.DiagnosticIdToProviderName["CS8300"].Single()); var logger = new TestTelemetryEventSink(); - using var _ = RoslynTelemetry.AddEventSink(logger); + using var _ = RoslynTelemetry.Current.AddEventSink(logger); TestTelemetryEventSink.TestScope scope; using (CopilotChangeAnalysisUtilities.LogCopilotChangeAnalysis("TestCode", accepted: true, "TestProposalId", result, CancellationToken.None)) { diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests_Telemetry.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests_Telemetry.cs index 5068de1d1a073..eadd77f2fd957 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests_Telemetry.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests_Telemetry.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -49,7 +49,7 @@ Task SetRemoteLoggingAsync(ImmutableArray loggerTypeNames, Immutab static bool AnythingIsListening(FunctionId functionId) { var listening = false; - RoslynTelemetry.Log(functionId, () => + RoslynTelemetry.Current.Log(functionId, () => { listening = true; return string.Empty; diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs index 579af07307d92..1a884b8296537 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs @@ -101,7 +101,7 @@ public static async Task CodeActionAsync( if (!RoslynString.IsNullOrEmpty(applyFix)) { var codeActionLogger = new CodeActionLogger(); - using var loggerRegistration = RoslynTelemetry.AddEventSink(codeActionLogger); + using var loggerRegistration = RoslynTelemetry.Current.AddEventSink(codeActionLogger); var result = await textViewWindowVerifier.TestServices.Editor.ApplyLightBulbActionAsync(applyFix, fixAllScope, blockUntilComplete, cancellationToken); diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs index f7e63341c3bcd..ce491d36ec733 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -91,7 +91,7 @@ public static void SetLoggers(IGlobalOptionService globalOptions, IThreadingCont static void Register(ref IDisposable registration, bool enabled, Func create) { - Interlocked.Exchange(ref registration, enabled ? RoslynTelemetry.AddEventSink(create()) : null)?.Dispose(); + Interlocked.Exchange(ref registration, enabled ? RoslynTelemetry.Current.AddEventSink(create()) : null)?.Dispose(); } } } diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/PerfMargin/PerfMarginPanel.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/PerfMargin/PerfMarginPanel.cs index 674866d1ef9e1..7ec1b74a7b3f9 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/PerfMargin/PerfMarginPanel.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/PerfMargin/PerfMarginPanel.cs @@ -32,7 +32,7 @@ public sealed class PerfMarginPanel : UserControl static PerfMarginPanel() { // Keep collecting into the process-wide model while the tool window is closed. - _ = RoslynTelemetry.AddEventSink(s_sink); + _ = RoslynTelemetry.Current.AddEventSink(s_sink); } public PerfMarginPanel() diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index 5617a1e0f2b99..81fab1f42548f 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -263,7 +263,7 @@ public Task> GetPreviewOperationsAsync(Cance internal async Task> GetPreviewOperationsAsync( Solution originalSolution, CancellationToken cancellationToken) { - using var _ = RoslynTelemetry.RecordBlockTime(FunctionId.SuggestedAction_Preview_Summary, $"Total"); + using var _ = RoslynTelemetry.Current.RecordBlockTime(FunctionId.SuggestedAction_Preview_Summary, $"Total"); var operations = await this.ComputePreviewOperationsAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Log/RoslynTelemetry.Workspaces.cs b/src/Workspaces/Core/Portable/Log/RoslynTelemetry.Workspaces.cs index 4314213b0aefc..005cf28d633bd 100644 --- a/src/Workspaces/Core/Portable/Log/RoslynTelemetry.Workspaces.cs +++ b/src/Workspaces/Core/Portable/Log/RoslynTelemetry.Workspaces.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -21,7 +21,7 @@ internal static class TelemetryKeys /// The parts of that need , which lives in /// the Workspaces layer and so is not available in the shared layer. /// -internal static partial class RoslynTelemetry +internal sealed partial class RoslynTelemetry { /// /// Posts a discrete event carrying the wall-clock duration of the returned scope, but only if it @@ -29,16 +29,16 @@ internal static partial class RoslynTelemetry /// this is not aggregated - each occurrence is /// its own event. /// - public static IDisposable? LogBlockTime(FunctionId functionId, KeyValueLogMessage logMessage, int minThresholdMs = -1) + public IDisposable? LogBlockTime(FunctionId functionId, KeyValueLogMessage logMessage, int minThresholdMs = -1) { if (TryGetEnabledSinks(functionId, out _)) - return new TimedEventBlock(functionId, logMessage, minThresholdMs); + return new TimedEventBlock(this, functionId, logMessage, minThresholdMs); logMessage.Free(); return null; } - private sealed class TimedEventBlock(FunctionId functionId, KeyValueLogMessage logMessage, int minThresholdMs) : IDisposable + private sealed class TimedEventBlock(RoslynTelemetry telemetry, FunctionId functionId, KeyValueLogMessage logMessage, int minThresholdMs) : IDisposable { private readonly SharedStopwatch _stopwatch = SharedStopwatch.StartNew(); @@ -59,7 +59,7 @@ public void Dispose() if (IsDebugging) message.Free(); else - Log(functionId, message); + telemetry.Log(functionId, message); } logMessage.Free(); diff --git a/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs b/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs index 56eee44944859..22bef420c33f3 100644 --- a/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs +++ b/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs @@ -1,10 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.UnitTests.Logging; using Xunit; @@ -39,6 +40,7 @@ private sealed class RecordingMetricSink : IMetricSink { public List CounterTagCounts { get; } = []; public List DistributionTagCounts { get; } = []; + public int FlushCount { get; private set; } public void Count(string eventName, string metricName, long delta, ReadOnlySpan> tags) => CounterTagCounts.Add(tags.Length); @@ -47,17 +49,143 @@ public void Record(string eventName, string metricName, long value, ReadOnlySpan => DistributionTagCounts.Add(tags.Length); public void Flush() + => FlushCount++; + } + + [Fact] + public void CurrentReturnsDefaultInstance() + { + var telemetry = RoslynTelemetry.Current; + + Assert.Same(telemetry, RoslynTelemetry.Current); + } + + [Fact] + public void SetCurrentRoutesTelemetryAndRestoresPreviousInstance() + { + var defaultTelemetry = RoslynTelemetry.Current; + var firstTelemetry = new RoslynTelemetry(); + var secondTelemetry = new RoslynTelemetry(); + var defaultEventSink = new RecordingSink(); + var firstEventSink = new RecordingSink(); + var secondEventSink = new RecordingSink(); + var defaultMetricSink = new RecordingMetricSink(); + var firstMetricSink = new RecordingMetricSink(); + var secondMetricSink = new RecordingMetricSink(); + + using var defaultEventRegistration = defaultTelemetry.AddEventSink(defaultEventSink); + using var firstEventRegistration = firstTelemetry.AddEventSink(firstEventSink); + using var secondEventRegistration = secondTelemetry.AddEventSink(secondEventSink); + using var defaultMetricRegistration = defaultTelemetry.AddMetricSink(defaultMetricSink); + using var firstMetricRegistration = firstTelemetry.AddMetricSink(firstMetricSink); + using var secondMetricRegistration = secondTelemetry.AddMetricSink(secondMetricSink); + + LogAll(); + using (RoslynTelemetry.SetCurrent(firstTelemetry)) + { + Assert.Same(firstTelemetry, RoslynTelemetry.Current); + LogAll(); + + using (RoslynTelemetry.SetCurrent(secondTelemetry)) + { + Assert.Same(secondTelemetry, RoslynTelemetry.Current); + LogAll(); + } + + Assert.Same(firstTelemetry, RoslynTelemetry.Current); + LogAll(); + } + + Assert.Same(defaultTelemetry, RoslynTelemetry.Current); + LogAll(); + + Assert.Equal(2, defaultEventSink.Events.Count); + Assert.Equal(2, firstEventSink.Events.Count); + Assert.Single(secondEventSink.Events); + Assert.Equal(2, defaultMetricSink.CounterTagCounts.Count); + Assert.Equal(2, firstMetricSink.CounterTagCounts.Count); + Assert.Single(secondMetricSink.CounterTagCounts); + Assert.Equal(2, defaultMetricSink.DistributionTagCounts.Count); + Assert.Equal(2, firstMetricSink.DistributionTagCounts.Count); + Assert.Single(secondMetricSink.DistributionTagCounts); + + static void LogAll() + { + RoslynTelemetry.Current.Log(FunctionId.TestEvent_NotUsed); + RoslynTelemetry.Current.Count(FunctionId.TestEvent_NotUsed, "Count"); + RoslynTelemetry.Current.Record(FunctionId.TestEvent_NotUsed, "Record", 1); + } + } + + [Fact] + public async Task CurrentFlowsAcrossAwaitAndTaskRun() + { + var previousTelemetry = RoslynTelemetry.Current; + var telemetry = new RoslynTelemetry(); + var sink = new RecordingSink(); + using var registration = telemetry.AddEventSink(sink); + + using (RoslynTelemetry.SetCurrent(telemetry)) + { + await Task.Yield(); + Assert.Same(telemetry, RoslynTelemetry.Current); + + await Task.Run(() => + { + Assert.Same(telemetry, RoslynTelemetry.Current); + Logger.Log(FunctionId.TestEvent_NotUsed); + }); + } + + Assert.Same(previousTelemetry, RoslynTelemetry.Current); + Assert.Single(sink.Events); + } + + [Fact] + public void FlushOnlyFlushesCurrentInstance() + { + var firstTelemetry = new RoslynTelemetry(); + var secondTelemetry = new RoslynTelemetry(); + var firstSink = new RecordingMetricSink(); + var secondSink = new RecordingMetricSink(); + using var firstRegistration = firstTelemetry.AddMetricSink(firstSink); + using var secondRegistration = secondTelemetry.AddMetricSink(secondSink); + + firstTelemetry.Flush(); + + Assert.Equal(1, firstSink.FlushCount); + Assert.Equal(0, secondSink.FlushCount); + } + + [Fact] + public void BlockEndUsesSinksCapturedAtStart() + { + var firstTelemetry = new RoslynTelemetry(); + var secondTelemetry = new RoslynTelemetry(); + var firstSink = new RecordingSink(); + var secondSink = new RecordingSink(); + using var firstRegistration = firstTelemetry.AddEventSink(firstSink); + using var secondRegistration = secondTelemetry.AddEventSink(secondSink); + + using (RoslynTelemetry.SetCurrent(firstTelemetry)) { + var block = Logger.LogBlock(FunctionId.TestEvent_NotUsed, CancellationToken.None); + + using (RoslynTelemetry.SetCurrent(secondTelemetry)) + block.Dispose(); } + + Assert.Equal(["Start", "End"], firstSink.Events.ConvertAll(e => e.Kind)); + Assert.Empty(secondSink.Events); } [Fact] public void BlockStartAndEndAreDeliveredAsAPair() { var sink = new RecordingSink(); - using var _ = RoslynTelemetry.AddEventSink(sink); + using var _ = RoslynTelemetry.Current.AddEventSink(sink); - using (RoslynTelemetry.LogBlock(FunctionId.TestEvent_NotUsed, CancellationToken.None)) + using (RoslynTelemetry.Current.LogBlock(FunctionId.TestEvent_NotUsed, CancellationToken.None)) { } @@ -71,12 +199,12 @@ public void BlockStartAndEndAreDeliveredAsAPair() public void ASinkRegisteredDuringABlockDoesNotSeeAnUnpairedEnd() { var first = new RecordingSink(); - using var _1 = RoslynTelemetry.AddEventSink(first); + using var _1 = RoslynTelemetry.Current.AddEventSink(first); var late = new RecordingSink(); - using (RoslynTelemetry.LogBlock(FunctionId.TestEvent_NotUsed, CancellationToken.None)) + using (RoslynTelemetry.Current.LogBlock(FunctionId.TestEvent_NotUsed, CancellationToken.None)) { - using var _2 = RoslynTelemetry.AddEventSink(late); + using var _2 = RoslynTelemetry.Current.AddEventSink(late); } Assert.Equal(["Start", "End"], first.Events.ConvertAll(e => e.Kind)); @@ -87,10 +215,10 @@ public void ASinkRegisteredDuringABlockDoesNotSeeAnUnpairedEnd() public void NothingIsDeliveredWhenEverySinkIsDisabled() { var sink = new RecordingSink { Enabled = false }; - using var _ = RoslynTelemetry.AddEventSink(sink); + using var _ = RoslynTelemetry.Current.AddEventSink(sink); - RoslynTelemetry.Log(FunctionId.TestEvent_NotUsed, "message"); - using (RoslynTelemetry.LogBlock(FunctionId.TestEvent_NotUsed, CancellationToken.None)) + RoslynTelemetry.Current.Log(FunctionId.TestEvent_NotUsed, "message"); + using (RoslynTelemetry.Current.LogBlock(FunctionId.TestEvent_NotUsed, CancellationToken.None)) { } @@ -102,10 +230,10 @@ public void DisposingARegistrationUnregistersTheSink() { var sink = new RecordingSink(); - var registration = RoslynTelemetry.AddEventSink(sink); - RoslynTelemetry.Log(FunctionId.TestEvent_NotUsed, "before"); + var registration = RoslynTelemetry.Current.AddEventSink(sink); + RoslynTelemetry.Current.Log(FunctionId.TestEvent_NotUsed, "before"); registration.Dispose(); - RoslynTelemetry.Log(FunctionId.TestEvent_NotUsed, "after"); + RoslynTelemetry.Current.Log(FunctionId.TestEvent_NotUsed, "after"); Assert.Single(sink.Events); @@ -117,14 +245,14 @@ public void DisposingARegistrationUnregistersTheSink() public void MetricOverloadsAcceptSingleAndDynamicTags() { var sink = new RecordingMetricSink(); - using var _ = RoslynTelemetry.AddMetricSink(sink); + using var _ = RoslynTelemetry.Current.AddMetricSink(sink); - RoslynTelemetry.Count(FunctionId.TestEvent_NotUsed, "Count", 1, new("kind", "single")); - RoslynTelemetry.Record(FunctionId.TestEvent_NotUsed, "Duration", 1, new("kind", "single")); + RoslynTelemetry.Current.Count(FunctionId.TestEvent_NotUsed, "Count", 1, new("kind", "single")); + RoslynTelemetry.Current.Record(FunctionId.TestEvent_NotUsed, "Duration", 1, new("kind", "single")); ReadOnlySpan> tags = [new("first", 1), new("second", 2)]; - RoslynTelemetry.Count(FunctionId.TestEvent_NotUsed, "Count", 1, tags); - RoslynTelemetry.Record(FunctionId.TestEvent_NotUsed, "Duration", 1, tags); + RoslynTelemetry.Current.Count(FunctionId.TestEvent_NotUsed, "Count", 1, tags); + RoslynTelemetry.Current.Record(FunctionId.TestEvent_NotUsed, "Duration", 1, tags); Assert.Equal([1, 2], sink.CounterTagCounts); Assert.Equal([1, 2], sink.DistributionTagCounts); @@ -141,16 +269,16 @@ public void MetricOverloadsAcceptSingleAndDynamicTags() public void MessagePassingOverloadsFreeTheMessage(bool anySinkEnabled) { var sink = new RecordingSink { Enabled = anySinkEnabled }; - using var _ = RoslynTelemetry.AddEventSink(sink); + using var _ = RoslynTelemetry.Current.AddEventSink(sink); - AssertReturnedToPool(message => RoslynTelemetry.Log(FunctionId.TestEvent_NotUsed, message)); + AssertReturnedToPool(message => RoslynTelemetry.Current.Log(FunctionId.TestEvent_NotUsed, message)); AssertReturnedToPool(message => { - using var block = RoslynTelemetry.LogBlock(FunctionId.TestEvent_NotUsed, message, CancellationToken.None); + using var block = RoslynTelemetry.Current.LogBlock(FunctionId.TestEvent_NotUsed, message, CancellationToken.None); }); AssertReturnedToPool(message => { - using var block = RoslynTelemetry.LogBlockTime(FunctionId.TestEvent_NotUsed, message); + using var block = RoslynTelemetry.Current.LogBlockTime(FunctionId.TestEvent_NotUsed, message); }); static void AssertReturnedToPool(Action log) @@ -174,12 +302,12 @@ static void AssertReturnedToPool(Action log) public void BlockEndCarriesDeltaOnlyWhenLogDeltaIsSet(bool logDelta) { var logger = new TestTelemetryEventSink(logDelta); - using var _ = RoslynTelemetry.AddEventSink(logger); + using var _ = RoslynTelemetry.Current.AddEventSink(logger); TestTelemetryEventSink.TestScope scope; // LogType.UserAction carries LogLevel.Information; anything lower is dropped by the sink. - using (RoslynTelemetry.LogBlock( + using (RoslynTelemetry.Current.LogBlock( FunctionId.TestEvent_NotUsed, KeyValueLogMessage.Create(LogType.UserAction), CancellationToken.None)) diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index 4834f000cc2a8..5a21591b34013 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -62,7 +62,7 @@ public ValueTask SynchronizeTextChangesAsync( var wasSynchronized = await SynchronizeTextChangesHelperAsync().ConfigureAwait(false); var metricName = wasSynchronized ? SynchronizeTextChangesAsyncSucceededMetricName : SynchronizeTextChangesAsyncFailedMetricName; - RoslynTelemetry.Count(FunctionId.RemoteHostService_SynchronizeTextAsyncStatus, metricName, 1); + RoslynTelemetry.Current.Count(FunctionId.RemoteHostService_SynchronizeTextAsyncStatus, metricName, 1); return; diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs index bc0a06c46cc95..9b889393d0696 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs @@ -68,7 +68,7 @@ public void AddSnapshot(IEnumerable snapshot, int unitC var delay = (long)perfInfo.TimeSpan.TotalMilliseconds; - RoslynTelemetry.Record(FunctionId.PerformAnalysis_Summary, "IndividualTimes", delay); + RoslynTelemetry.Current.Record(FunctionId.PerformAnalysis_Summary, "IndividualTimes", delay); if (delay > PerformAnalysisTelemetryDelay) { @@ -85,7 +85,7 @@ public void AddSnapshot(IEnumerable snapshot, int unitC m[ForSpanAnalysis] = forSpanAnalysis; }); - RoslynTelemetry.Log(FunctionId.PerformAnalysis_Delay, logMessage); + RoslynTelemetry.Current.Log(FunctionId.PerformAnalysis_Delay, logMessage); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs b/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs index fc1c848686d06..30f645f7a18ea 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -90,6 +90,6 @@ public ValueTask EnableLoggingAsync(ImmutableArray loggerTypeNames, Immu // Its predicate is a snapshot of the per-FunctionId options, so a fresh sink is built on every // apply. Mirrors the Performance Loggers page, which is the only way these get enabled. static void Register(ref IDisposable? registration, bool enabled, Func create) - => Interlocked.Exchange(ref registration, enabled ? RoslynTelemetry.AddEventSink(create()) : null)?.Dispose(); + => Interlocked.Exchange(ref registration, enabled ? RoslynTelemetry.Current.AddEventSink(create()) : null)?.Dispose(); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs index 380a7f6e2242b..22262f4703e7b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs @@ -14,61 +14,61 @@ internal static class Logger { /// public static void Log(FunctionId functionId, string? message = null, LogLevel logLevel = LogLevel.Debug) - => RoslynTelemetry.Log(functionId, message, logLevel); + => RoslynTelemetry.Current.Log(functionId, message, logLevel); /// public static void Log(FunctionId functionId, Func messageGetter, LogLevel logLevel = LogLevel.Debug) - => RoslynTelemetry.Log(functionId, messageGetter, logLevel); + => RoslynTelemetry.Current.Log(functionId, messageGetter, logLevel); /// public static void Log(FunctionId functionId, Func messageGetter, TArg arg, LogLevel logLevel = LogLevel.Debug) - => RoslynTelemetry.Log(functionId, messageGetter, arg, logLevel); + => RoslynTelemetry.Current.Log(functionId, messageGetter, arg, logLevel); /// public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, LogLevel logLevel = LogLevel.Debug) - => RoslynTelemetry.Log(functionId, messageGetter, arg0, arg1, logLevel); + => RoslynTelemetry.Current.Log(functionId, messageGetter, arg0, arg1, logLevel); /// public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, LogLevel logLevel = LogLevel.Debug) - => RoslynTelemetry.Log(functionId, messageGetter, arg0, arg1, arg2, logLevel); + => RoslynTelemetry.Current.Log(functionId, messageGetter, arg0, arg1, arg2, logLevel); /// public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, LogLevel logLevel = LogLevel.Debug) - => RoslynTelemetry.Log(functionId, messageGetter, arg0, arg1, arg2, arg3, logLevel); + => RoslynTelemetry.Current.Log(functionId, messageGetter, arg0, arg1, arg2, arg3, logLevel); /// public static void Log(FunctionId functionId, LogMessage logMessage) - => RoslynTelemetry.Log(functionId, logMessage); + => RoslynTelemetry.Current.Log(functionId, logMessage); /// public static IDisposable LogBlock(FunctionId functionId, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => RoslynTelemetry.LogBlock(functionId, token, logLevel); + => RoslynTelemetry.Current.LogBlock(functionId, token, logLevel); /// public static IDisposable LogBlock(FunctionId functionId, string? message, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => RoslynTelemetry.LogBlock(functionId, message, token, logLevel); + => RoslynTelemetry.Current.LogBlock(functionId, message, token, logLevel); /// public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => RoslynTelemetry.LogBlock(functionId, messageGetter, token, logLevel); + => RoslynTelemetry.Current.LogBlock(functionId, messageGetter, token, logLevel); /// public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg arg, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => RoslynTelemetry.LogBlock(functionId, messageGetter, arg, token, logLevel); + => RoslynTelemetry.Current.LogBlock(functionId, messageGetter, arg, token, logLevel); /// public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => RoslynTelemetry.LogBlock(functionId, messageGetter, arg0, arg1, token, logLevel); + => RoslynTelemetry.Current.LogBlock(functionId, messageGetter, arg0, arg1, token, logLevel); /// public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => RoslynTelemetry.LogBlock(functionId, messageGetter, arg0, arg1, arg2, token, logLevel); + => RoslynTelemetry.Current.LogBlock(functionId, messageGetter, arg0, arg1, arg2, token, logLevel); /// public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => RoslynTelemetry.LogBlock(functionId, messageGetter, arg0, arg1, arg2, arg3, token, logLevel); + => RoslynTelemetry.Current.LogBlock(functionId, messageGetter, arg0, arg1, arg2, arg3, token, logLevel); /// public static IDisposable LogBlock(FunctionId functionId, LogMessage logMessage, CancellationToken token) - => RoslynTelemetry.LogBlock(functionId, logMessage, token); + => RoslynTelemetry.Current.LogBlock(functionId, logMessage, token); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs index d31da3d253cd1..c1d772050986c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs @@ -14,27 +14,58 @@ namespace Microsoft.CodeAnalysis.Internal.Log; /// or . When no sinks are registered calls are /// cheap no-ops. /// -internal static partial class RoslynTelemetry +internal sealed partial class RoslynTelemetry { + private static readonly AsyncLocal s_current = new(); + private static readonly RoslynTelemetry s_default = new(); + + // Counting active scopes rather than instances means default-only hosts never read the AsyncLocal. + // The count is incremented before setting the ambient and decremented after restoring it, so a + // live scope cannot observe zero and incorrectly take the fast path. + private static int s_currentScopeCount; + /// /// The registered each event fans out to. /// - private static ImmutableArray s_eventSinks = []; + private ImmutableArray _eventSinks = []; /// /// next unique block id that will be given to each LogBlock /// private static int s_lastUniqueBlockId; + public static RoslynTelemetry Current + => Volatile.Read(ref s_currentScopeCount) == 0 ? s_default : s_current.Value ?? s_default; + + /// + /// Sets the telemetry instance for the current asynchronous control flow. Disposing the result + /// restores the previous instance. + /// + public static IDisposable SetCurrent(RoslynTelemetry telemetry) + { + Contract.ThrowIfNull(telemetry); + + var previous = s_current.Value; + var registration = new Registration(() => + { + s_current.Value = previous; + Interlocked.Decrement(ref s_currentScopeCount); + }); + + Interlocked.Increment(ref s_currentScopeCount); + s_current.Value = telemetry; + return registration; + } + /// /// Registers to receive events. A sink instance may have only one active /// registration. Dispose the result to unregister it; a host that keeps its sinks for the life of /// the process can simply never dispose. /// - public static IDisposable AddEventSink(IEventSink sink) + public IDisposable AddEventSink(IEventSink sink) { - ImmutableInterlocked.Update(ref s_eventSinks, static (sinks, sink) => AddSink(sinks, sink), sink); - return new Registration(() => ImmutableInterlocked.Update(ref s_eventSinks, static (sinks, sink) => sinks.Remove(sink, ReferenceEqualityComparer.Instance), sink)); + ImmutableInterlocked.Update(ref _eventSinks, static (sinks, sink) => AddSink(sinks, sink), sink); + return new Registration(() => ImmutableInterlocked.Update(ref _eventSinks, static (sinks, sink) => sinks.Remove(sink, ReferenceEqualityComparer.Instance), sink)); } private static ImmutableArray AddSink(ImmutableArray sinks, TSink sink) @@ -59,9 +90,9 @@ public void Dispose() /// is constructed, so that logging costs nothing when everything is /// disabled. /// - private static bool TryGetEnabledSinks(FunctionId functionId, out ImmutableArray sinks) + private bool TryGetEnabledSinks(FunctionId functionId, out ImmutableArray sinks) { - sinks = s_eventSinks; + sinks = _eventSinks; foreach (var sink in sinks) { @@ -84,19 +115,21 @@ private static void LogToSinks(ImmutableArray sinks, FunctionId func internal static class TestAccessor { /// - /// Unregisters every sink, so that one test cannot leak a sink into the next. + /// Resets the default sinks and ambient state so one test cannot leak telemetry into the next. /// public static void RemoveAllSinks() { - ImmutableInterlocked.InterlockedExchange(ref s_eventSinks, []); - ImmutableInterlocked.InterlockedExchange(ref s_metricSinks, []); + ImmutableInterlocked.InterlockedExchange(ref s_default._eventSinks, []); + ImmutableInterlocked.InterlockedExchange(ref s_default._metricSinks, []); + s_current.Value = null; + Interlocked.Exchange(ref s_currentScopeCount, 0); } } /// /// log a specific event with a simple context message which should be very cheap to create /// - public static void Log(FunctionId functionId, string? message = null, LogLevel logLevel = LogLevel.Debug) + public void Log(FunctionId functionId, string? message = null, LogLevel logLevel = LogLevel.Debug) { if (TryGetEnabledSinks(functionId, out var sinks)) { @@ -111,7 +144,7 @@ public static void Log(FunctionId functionId, string? message = null, LogLevel l /// log a specific event with a context message that will only be created when it is needed. /// the messageGetter should be cheap to create. in another word, it shouldn't capture any locals /// - public static void Log(FunctionId functionId, Func messageGetter, LogLevel logLevel = LogLevel.Debug) + public void Log(FunctionId functionId, Func messageGetter, LogLevel logLevel = LogLevel.Debug) { if (TryGetEnabledSinks(functionId, out var sinks)) { @@ -126,7 +159,7 @@ public static void Log(FunctionId functionId, Func messageGetter, LogLev /// log a specific event with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static void Log(FunctionId functionId, Func messageGetter, TArg arg, LogLevel logLevel = LogLevel.Debug) + public void Log(FunctionId functionId, Func messageGetter, TArg arg, LogLevel logLevel = LogLevel.Debug) { if (TryGetEnabledSinks(functionId, out var sinks)) { @@ -140,7 +173,7 @@ public static void Log(FunctionId functionId, Func messageGe /// log a specific event with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, LogLevel logLevel = LogLevel.Debug) + public void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, LogLevel logLevel = LogLevel.Debug) { if (TryGetEnabledSinks(functionId, out var sinks)) { @@ -154,7 +187,7 @@ public static void Log(FunctionId functionId, Func - public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, LogLevel logLevel = LogLevel.Debug) + public void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, LogLevel logLevel = LogLevel.Debug) { if (TryGetEnabledSinks(functionId, out var sinks)) { @@ -168,7 +201,7 @@ public static void Log(FunctionId functionId, Func - public static void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, LogLevel logLevel = LogLevel.Debug) + public void Log(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, LogLevel logLevel = LogLevel.Debug) { if (TryGetEnabledSinks(functionId, out var sinks)) { @@ -181,7 +214,7 @@ public static void Log(FunctionId functionId, Func /// log a specific event with a context message. /// - public static void Log(FunctionId functionId, LogMessage logMessage) + public void Log(FunctionId functionId, LogMessage logMessage) { if (TryGetEnabledSinks(functionId, out var sinks)) { @@ -202,13 +235,13 @@ private static int GetNextUniqueBlockId() /// /// simplest way to log a start and end pair /// - public static IDisposable LogBlock(FunctionId functionId, CancellationToken token, LogLevel logLevel = LogLevel.Trace) + public IDisposable LogBlock(FunctionId functionId, CancellationToken token, LogLevel logLevel = LogLevel.Trace) => LogBlock(functionId, string.Empty, token, logLevel); /// /// simplest way to log a start and end pair with a simple context message which should be very cheap to create /// - public static IDisposable LogBlock(FunctionId functionId, string? message, CancellationToken token, LogLevel logLevel = LogLevel.Trace) + public IDisposable LogBlock(FunctionId functionId, string? message, CancellationToken token, LogLevel logLevel = LogLevel.Trace) => TryGetEnabledSinks(functionId, out var sinks) ? CreateLogBlock(sinks, functionId, LogMessage.Create(message ?? "", logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; @@ -217,7 +250,7 @@ public static IDisposable LogBlock(FunctionId functionId, string? message, Cance /// log a start and end pair with a context message that will only be created when it is needed. /// the messageGetter should be cheap to create. in another word, it shouldn't capture any locals /// - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, CancellationToken token, LogLevel logLevel = LogLevel.Trace) + public IDisposable LogBlock(FunctionId functionId, Func messageGetter, CancellationToken token, LogLevel logLevel = LogLevel.Trace) => TryGetEnabledSinks(functionId, out var sinks) ? CreateLogBlock(sinks, functionId, LogMessage.Create(messageGetter, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; @@ -226,7 +259,7 @@ public static IDisposable LogBlock(FunctionId functionId, Func messageGe /// log a start and end pair with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg arg, CancellationToken token, LogLevel logLevel = LogLevel.Trace) + public IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg arg, CancellationToken token, LogLevel logLevel = LogLevel.Trace) => TryGetEnabledSinks(functionId, out var sinks) ? CreateLogBlock(sinks, functionId, LogMessage.Create(messageGetter, arg, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; @@ -235,7 +268,7 @@ public static IDisposable LogBlock(FunctionId functionId, Func - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, CancellationToken token, LogLevel logLevel = LogLevel.Trace) + public IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, CancellationToken token, LogLevel logLevel = LogLevel.Trace) => TryGetEnabledSinks(functionId, out var sinks) ? CreateLogBlock(sinks, functionId, LogMessage.Create(messageGetter, arg0, arg1, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; @@ -244,7 +277,7 @@ public static IDisposable LogBlock(FunctionId functionId, Func - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, CancellationToken token, LogLevel logLevel = LogLevel.Trace) + public IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, CancellationToken token, LogLevel logLevel = LogLevel.Trace) => TryGetEnabledSinks(functionId, out var sinks) ? CreateLogBlock(sinks, functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; @@ -253,7 +286,7 @@ public static IDisposable LogBlock(FunctionId functionId, F /// log a start and end pair with a context message that requires some arguments to be created when requested. /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// - public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, CancellationToken token, LogLevel logLevel = LogLevel.Trace) + public IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, CancellationToken token, LogLevel logLevel = LogLevel.Trace) => TryGetEnabledSinks(functionId, out var sinks) ? CreateLogBlock(sinks, functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, arg3, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; @@ -262,7 +295,7 @@ public static IDisposable LogBlock(FunctionId functi /// log a start and end pair with a context message. Takes ownership of /// whether or not anything is listening. /// - public static IDisposable LogBlock(FunctionId functionId, LogMessage logMessage, CancellationToken token) + public IDisposable LogBlock(FunctionId functionId, LogMessage logMessage, CancellationToken token) { if (TryGetEnabledSinks(functionId, out var sinks)) return CreateLogBlock(sinks, functionId, logMessage, GetNextUniqueBlockId(), token); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_LogBlock.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_LogBlock.cs index 79890ab57a334..f81df180e015d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_LogBlock.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_LogBlock.cs @@ -10,14 +10,14 @@ namespace Microsoft.CodeAnalysis.Internal.Log; -internal static partial class RoslynTelemetry +internal sealed partial class RoslynTelemetry { // Regardless of how many tasks we can run in parallel on the machine, we likely won't need more than 256 // instrumentation points in flight at a given time. // Use an object pool since we may be logging up to 1-10k events/second private static readonly ObjectPool s_pool = new(() => new RoslynLogBlock(s_pool!), Math.Min(Environment.ProcessorCount * 8, 256)); - public static IDisposable CreateLogBlock(ImmutableArray sinks, FunctionId functionId, LogMessage message, int blockId, CancellationToken cancellationToken) + private static IDisposable CreateLogBlock(ImmutableArray sinks, FunctionId functionId, LogMessage message, int blockId, CancellationToken cancellationToken) { var block = s_pool.Allocate(); block.Construct(sinks, functionId, message, blockId, cancellationToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_Metrics.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_Metrics.cs index 93a075ffdfcc8..71b7594f07cf5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_Metrics.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_Metrics.cs @@ -11,53 +11,53 @@ namespace Microsoft.CodeAnalysis.Internal.Log; -internal static partial class RoslynTelemetry +internal sealed partial class RoslynTelemetry { /// /// The registered each metric fans out to. /// - private static ImmutableArray s_metricSinks = []; + private ImmutableArray _metricSinks = []; /// /// Registers to receive measurements. A sink instance may have only one /// active registration. Dispose the result to unregister it; a host that keeps its sink for the /// life of the process can simply never dispose. /// - public static IDisposable AddMetricSink(IMetricSink sink) + public IDisposable AddMetricSink(IMetricSink sink) { - ImmutableInterlocked.Update(ref s_metricSinks, static (sinks, sink) => AddSink(sinks, sink), sink); - return new Registration(() => ImmutableInterlocked.Update(ref s_metricSinks, static (sinks, sink) => sinks.Remove(sink, ReferenceEqualityComparer.Instance), sink)); + ImmutableInterlocked.Update(ref _metricSinks, static (sinks, sink) => AddSink(sinks, sink), sink); + return new Registration(() => ImmutableInterlocked.Update(ref _metricSinks, static (sinks, sink) => sinks.Remove(sink, ReferenceEqualityComparer.Instance), sink)); } /// /// Posts all pending aggregated measurements. /// - public static void Flush() + public void Flush() { - foreach (var sink in s_metricSinks) + foreach (var sink in _metricSinks) sink.Flush(); } #region Counters - public static void Count(FunctionId functionId, string metricName, long delta = 1) + public void Count(FunctionId functionId, string metricName, long delta = 1) { CountCore(functionId, metricName, delta, default); } - public static void Count(FunctionId functionId, string metricName, long delta, KeyValuePair tag) + public void Count(FunctionId functionId, string metricName, long delta, KeyValuePair tag) { Span> tags = [tag]; CountCore(functionId, metricName, delta, tags); } - public static void Count(FunctionId functionId, string metricName, long delta, KeyValuePair tag1, KeyValuePair tag2) + public void Count(FunctionId functionId, string metricName, long delta, KeyValuePair tag1, KeyValuePair tag2) { Span> tags = [tag1, tag2]; CountCore(functionId, metricName, delta, tags); } - public static void Count(FunctionId functionId, string metricName, long delta, KeyValuePair tag1, KeyValuePair tag2, KeyValuePair tag3) + public void Count(FunctionId functionId, string metricName, long delta, KeyValuePair tag1, KeyValuePair tag2, KeyValuePair tag3) { Span> tags = [tag1, tag2, tag3]; CountCore(functionId, metricName, delta, tags); @@ -68,12 +68,12 @@ public static void Count(FunctionId functionId, string metricName, long delta, K /// priority allows target-typed new(...) to select the fixed-arity overloads above. /// [OverloadResolutionPriority(-1)] - public static void Count(FunctionId functionId, string metricName, long delta, ReadOnlySpan> tags) + public void Count(FunctionId functionId, string metricName, long delta, ReadOnlySpan> tags) => CountCore(functionId, metricName, delta, tags); - private static void CountCore(FunctionId functionId, string metricName, long delta, ReadOnlySpan> tags) + private void CountCore(FunctionId functionId, string metricName, long delta, ReadOnlySpan> tags) { - var sinks = s_metricSinks; + var sinks = _metricSinks; if (sinks.IsEmpty) return; @@ -86,36 +86,36 @@ private static void CountCore(FunctionId functionId, string metricName, long del #region Distributions - public static void Record(FunctionId functionId, string metricName, long value) + public void Record(FunctionId functionId, string metricName, long value) { RecordCore(functionId, metricName, value, default); } - public static void Record(FunctionId functionId, string metricName, long value, KeyValuePair tag) + public void Record(FunctionId functionId, string metricName, long value, KeyValuePair tag) { Span> tags = [tag]; RecordCore(functionId, metricName, value, tags); } - public static void Record(FunctionId functionId, string metricName, long value, KeyValuePair tag1, KeyValuePair tag2) + public void Record(FunctionId functionId, string metricName, long value, KeyValuePair tag1, KeyValuePair tag2) { Span> tags = [tag1, tag2]; RecordCore(functionId, metricName, value, tags); } - public static void Record(FunctionId functionId, string metricName, long value, KeyValuePair tag1, KeyValuePair tag2, KeyValuePair tag3) + public void Record(FunctionId functionId, string metricName, long value, KeyValuePair tag1, KeyValuePair tag2, KeyValuePair tag3) { Span> tags = [tag1, tag2, tag3]; RecordCore(functionId, metricName, value, tags); } [OverloadResolutionPriority(-1)] - public static void Record(FunctionId functionId, string metricName, long value, ReadOnlySpan> tags) + public void Record(FunctionId functionId, string metricName, long value, ReadOnlySpan> tags) => RecordCore(functionId, metricName, value, tags); - private static void RecordCore(FunctionId functionId, string metricName, long value, ReadOnlySpan> tags) + private void RecordCore(FunctionId functionId, string metricName, long value, ReadOnlySpan> tags) { - var sinks = s_metricSinks; + var sinks = _metricSinks; if (sinks.IsEmpty) return; @@ -136,8 +136,8 @@ private static void RecordCore(FunctionId functionId, string metricName, long va /// a compound name. /// /// - public static IDisposable? RecordBlockTime(FunctionId functionId, string metricName) - => s_metricSinks.IsEmpty ? null : new TimedBlock(functionId, metricName); + public IDisposable? RecordBlockTime(FunctionId functionId, string metricName) + => _metricSinks.IsEmpty ? null : new TimedBlock(this, functionId, metricName); /// /// Whether measurements would be skewed by the environment rather than by the code being measured: @@ -155,7 +155,7 @@ private static bool IsDebugging } } - private sealed class TimedBlock(FunctionId functionId, string metricName) : IDisposable + private sealed class TimedBlock(RoslynTelemetry telemetry, FunctionId functionId, string metricName) : IDisposable { private readonly SharedStopwatch _stopwatch = SharedStopwatch.StartNew(); @@ -163,7 +163,7 @@ public void Dispose() { // Don't skew telemetry results by recording in debug bits or under a debugger. if (!IsDebugging) - RecordCore(functionId, metricName, (long)_stopwatch.Elapsed.TotalMilliseconds, default); + telemetry.RecordCore(functionId, metricName, (long)_stopwatch.Elapsed.TotalMilliseconds, default); } } } From 7ea36956dbf1fc85cde686896284224ef9d984a8 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Thu, 3 Sep 2026 13:26:31 -0700 Subject: [PATCH 2/5] Remove RoslynTelemetry ambient fast path Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 415c03c3-4e89-40bd-89c8-c28b3cab5da8 --- .../CoreTest/Log/RoslynTelemetryTests.cs | 23 +++++++++++++++++++ .../Compiler/Core/Log/RoslynTelemetry.cs | 17 ++------------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs b/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs index 22bef420c33f3..c8c3ddd80d7cd 100644 --- a/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs +++ b/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs @@ -141,6 +141,29 @@ await Task.Run(() => Assert.Single(sink.Events); } + [Fact] + public async Task CurrentFlowsToChildAfterParentScopeIsDisposed() + { + var previousTelemetry = RoslynTelemetry.Current; + var telemetry = new RoslynTelemetry(); + var releaseChild = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Task child; + + using (RoslynTelemetry.SetCurrent(telemetry)) + { + child = Task.Run(async () => + { + await releaseChild.Task; + return RoslynTelemetry.Current; + }); + } + + releaseChild.SetResult(true); + + Assert.Same(telemetry, await child); + Assert.Same(previousTelemetry, RoslynTelemetry.Current); + } + [Fact] public void FlushOnlyFlushesCurrentInstance() { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs index c1d772050986c..973981e609683 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs @@ -19,11 +19,6 @@ internal sealed partial class RoslynTelemetry private static readonly AsyncLocal s_current = new(); private static readonly RoslynTelemetry s_default = new(); - // Counting active scopes rather than instances means default-only hosts never read the AsyncLocal. - // The count is incremented before setting the ambient and decremented after restoring it, so a - // live scope cannot observe zero and incorrectly take the fast path. - private static int s_currentScopeCount; - /// /// The registered each event fans out to. /// @@ -35,7 +30,7 @@ internal sealed partial class RoslynTelemetry private static int s_lastUniqueBlockId; public static RoslynTelemetry Current - => Volatile.Read(ref s_currentScopeCount) == 0 ? s_default : s_current.Value ?? s_default; + => s_current.Value ?? s_default; /// /// Sets the telemetry instance for the current asynchronous control flow. Disposing the result @@ -46,15 +41,8 @@ public static IDisposable SetCurrent(RoslynTelemetry telemetry) Contract.ThrowIfNull(telemetry); var previous = s_current.Value; - var registration = new Registration(() => - { - s_current.Value = previous; - Interlocked.Decrement(ref s_currentScopeCount); - }); - - Interlocked.Increment(ref s_currentScopeCount); s_current.Value = telemetry; - return registration; + return new Registration(() => s_current.Value = previous); } /// @@ -122,7 +110,6 @@ public static void RemoveAllSinks() ImmutableInterlocked.InterlockedExchange(ref s_default._eventSinks, []); ImmutableInterlocked.InterlockedExchange(ref s_default._metricSinks, []); s_current.Value = null; - Interlocked.Exchange(ref s_currentScopeCount, 0); } } From 787cb2b5e54a7ba8e2aae25339fdff389cb0f435 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 9 Sep 2026 11:17:27 -0700 Subject: [PATCH 3/5] feedback --- .../LanguageServerRequestTelemetryTests.cs | 2 + .../AbstractWorkspaceTelemetryService.cs | 1 + .../CoreTest/Log/RoslynTelemetryTests.cs | 38 +------------------ .../Compiler/Core/Log/RoslynTelemetry.cs | 2 + 4 files changed, 7 insertions(+), 36 deletions(-) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs index e4e6d01f589e2..78aeb53f7ca78 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs @@ -36,6 +36,8 @@ public async Task RealRequestsProduceAggregatedTelemetry() { var poster = new RecordingPoster(); using var sink = VSMetricSink.TestAccessor.CreateSink(poster); + + using var telemetry = RoslynTelemetry.SetCurrent(new RoslynTelemetry()); using var registration = RoslynTelemetry.Current.AddMetricSink(sink); var server = await CreateLanguageServerAsync(); diff --git a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs index 6ef2cc53f625c..7313124694ab7 100644 --- a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs @@ -32,6 +32,7 @@ public void InitializeTelemetrySession(TelemetrySession telemetrySession, bool l Contract.ThrowIfFalse(CurrentSession is null); var metricSink = new VSMetricSink(telemetrySession); + Debug.Assert(RoslynTelemetry.IsDefault(RoslynTelemetry.Current)); _registrations = [ .. CreateEventSinks(telemetrySession, logDelta).SelectAsArray(RoslynTelemetry.Current.AddEventSink), diff --git a/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs b/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs index c8c3ddd80d7cd..7a30a6bd65003 100644 --- a/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs +++ b/src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs @@ -66,55 +66,21 @@ public void SetCurrentRoutesTelemetryAndRestoresPreviousInstance() var defaultTelemetry = RoslynTelemetry.Current; var firstTelemetry = new RoslynTelemetry(); var secondTelemetry = new RoslynTelemetry(); - var defaultEventSink = new RecordingSink(); - var firstEventSink = new RecordingSink(); - var secondEventSink = new RecordingSink(); - var defaultMetricSink = new RecordingMetricSink(); - var firstMetricSink = new RecordingMetricSink(); - var secondMetricSink = new RecordingMetricSink(); - - using var defaultEventRegistration = defaultTelemetry.AddEventSink(defaultEventSink); - using var firstEventRegistration = firstTelemetry.AddEventSink(firstEventSink); - using var secondEventRegistration = secondTelemetry.AddEventSink(secondEventSink); - using var defaultMetricRegistration = defaultTelemetry.AddMetricSink(defaultMetricSink); - using var firstMetricRegistration = firstTelemetry.AddMetricSink(firstMetricSink); - using var secondMetricRegistration = secondTelemetry.AddMetricSink(secondMetricSink); - - LogAll(); + + Assert.Same(defaultTelemetry, RoslynTelemetry.Current); using (RoslynTelemetry.SetCurrent(firstTelemetry)) { Assert.Same(firstTelemetry, RoslynTelemetry.Current); - LogAll(); using (RoslynTelemetry.SetCurrent(secondTelemetry)) { Assert.Same(secondTelemetry, RoslynTelemetry.Current); - LogAll(); } Assert.Same(firstTelemetry, RoslynTelemetry.Current); - LogAll(); } Assert.Same(defaultTelemetry, RoslynTelemetry.Current); - LogAll(); - - Assert.Equal(2, defaultEventSink.Events.Count); - Assert.Equal(2, firstEventSink.Events.Count); - Assert.Single(secondEventSink.Events); - Assert.Equal(2, defaultMetricSink.CounterTagCounts.Count); - Assert.Equal(2, firstMetricSink.CounterTagCounts.Count); - Assert.Single(secondMetricSink.CounterTagCounts); - Assert.Equal(2, defaultMetricSink.DistributionTagCounts.Count); - Assert.Equal(2, firstMetricSink.DistributionTagCounts.Count); - Assert.Single(secondMetricSink.DistributionTagCounts); - - static void LogAll() - { - RoslynTelemetry.Current.Log(FunctionId.TestEvent_NotUsed); - RoslynTelemetry.Current.Count(FunctionId.TestEvent_NotUsed, "Count"); - RoslynTelemetry.Current.Record(FunctionId.TestEvent_NotUsed, "Record", 1); - } } [Fact] diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs index 973981e609683..b6a4ac97d2769 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs @@ -45,6 +45,8 @@ public static IDisposable SetCurrent(RoslynTelemetry telemetry) return new Registration(() => s_current.Value = previous); } + public static bool IsDefault(RoslynTelemetry instance) => instance == s_default; + /// /// Registers to receive events. A sink instance may have only one active /// registration. Dispose the result to unregister it; a host that keeps its sinks for the life of From 949e99de4cb38bae10db55285c437f2956b165e0 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 9 Sep 2026 12:53:53 -0700 Subject: [PATCH 4/5] Restore generated FSharp resources Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 415c03c3-4e89-40bd-89c8-c28b3cab5da8 --- .../ExternalAccessFSharpResources.Designer.cs | 371 +++++++++--------- 1 file changed, 186 insertions(+), 185 deletions(-) diff --git a/src/VisualStudio/ExternalAccess/Core/FSharp/ExternalAccessFSharpResources.Designer.cs b/src/VisualStudio/ExternalAccess/Core/FSharp/ExternalAccessFSharpResources.Designer.cs index a6cb84251ac4a..54f8247c0c81b 100644 --- a/src/VisualStudio/ExternalAccess/Core/FSharp/ExternalAccessFSharpResources.Designer.cs +++ b/src/VisualStudio/ExternalAccess/Core/FSharp/ExternalAccessFSharpResources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -8,209 +8,210 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.VisualStudio.LanguageServices.ExternalAccess.FSharp { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class ExternalAccessFSharpResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ExternalAccessFSharpResources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.LanguageServices.ExternalAccess.FSharp.ExternalAccessFSharpResources", typeof(ExternalAccessFSharpResources).Assembly); - resourceMan = temp; - } - return resourceMan; +#nullable disable + +namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp; +using System; + + +/// +/// A strongly-typed resource class, for looking up localized strings, etc. +/// +// This class was auto-generated by the StronglyTypedResourceBuilder +// class via a tool like ResGen or Visual Studio. +// To add or remove a member, edit your .ResX file then rerun ResGen +// with the /str option, or rebuild your VS project. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] +[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +internal class ExternalAccessFSharpResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ExternalAccessFSharpResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.ExternalAccess.FSharp.ExternalAccessFSharpResources", typeof(ExternalAccessFSharpResources).Assembly); + resourceMan = temp; } + return resourceMan; } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; } - - /// - /// Looks up a localized string similar to Cannot determine the symbol under the caret. - /// - internal static string CannotDetermineSymbol { - get { - return ResourceManager.GetString("CannotDetermineSymbol", resourceCulture); - } + set { + resourceCulture = value; } - - /// - /// Looks up a localized string similar to Cannot navigate to the requested location. - /// - internal static string CannotNavigateUnknown { - get { - return ResourceManager.GetString("CannotNavigateUnknown", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Cannot determine the symbol under the caret. + /// + internal static string CannotDetermineSymbol { + get { + return ResourceManager.GetString("CannotDetermineSymbol", resourceCulture); } - - /// - /// Looks up a localized string similar to F# Disposable Types. - /// - internal static string FSharpDisposablesClassificationType { - get { - return ResourceManager.GetString("FSharpDisposablesClassificationType", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Cannot navigate to the requested location. + /// + internal static string CannotNavigateUnknown { + get { + return ResourceManager.GetString("CannotNavigateUnknown", resourceCulture); } - - /// - /// Looks up a localized string similar to F# Functions / Methods. - /// - internal static string FSharpFunctionsOrMethodsClassificationType { - get { - return ResourceManager.GetString("FSharpFunctionsOrMethodsClassificationType", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to F# Disposable Types. + /// + internal static string FSharpDisposablesClassificationType { + get { + return ResourceManager.GetString("FSharpDisposablesClassificationType", resourceCulture); } - - /// - /// Looks up a localized string similar to F# Mutable Variables / Reference Cells. - /// - internal static string FSharpMutableVarsClassificationType { - get { - return ResourceManager.GetString("FSharpMutableVarsClassificationType", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to F# Functions / Methods. + /// + internal static string FSharpFunctionsOrMethodsClassificationType { + get { + return ResourceManager.GetString("FSharpFunctionsOrMethodsClassificationType", resourceCulture); } - - /// - /// Looks up a localized string similar to F# Printf Format. - /// - internal static string FSharpPrintfFormatClassificationType { - get { - return ResourceManager.GetString("FSharpPrintfFormatClassificationType", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to F# Mutable Variables / Reference Cells. + /// + internal static string FSharpMutableVarsClassificationType { + get { + return ResourceManager.GetString("FSharpMutableVarsClassificationType", resourceCulture); } - - /// - /// Looks up a localized string similar to F# Properties. - /// - internal static string FSharpPropertiesClassificationType { - get { - return ResourceManager.GetString("FSharpPropertiesClassificationType", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to F# Printf Format. + /// + internal static string FSharpPrintfFormatClassificationType { + get { + return ResourceManager.GetString("FSharpPrintfFormatClassificationType", resourceCulture); } - - /// - /// Looks up a localized string similar to Implement interface. - /// - internal static string ImplementInterface { - get { - return ResourceManager.GetString("ImplementInterface", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to F# Properties. + /// + internal static string FSharpPropertiesClassificationType { + get { + return ResourceManager.GetString("FSharpPropertiesClassificationType", resourceCulture); } - - /// - /// Looks up a localized string similar to Implement interface without type annotation. - /// - internal static string ImplementInterfaceWithoutTypeAnnotation { - get { - return ResourceManager.GetString("ImplementInterfaceWithoutTypeAnnotation", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Implement interface. + /// + internal static string ImplementInterface { + get { + return ResourceManager.GetString("ImplementInterface", resourceCulture); } - - /// - /// Looks up a localized string similar to Locating the symbol under the caret.... - /// - internal static string LocatingSymbol { - get { - return ResourceManager.GetString("LocatingSymbol", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Implement interface without type annotation. + /// + internal static string ImplementInterfaceWithoutTypeAnnotation { + get { + return ResourceManager.GetString("ImplementInterfaceWithoutTypeAnnotation", resourceCulture); } - - /// - /// Looks up a localized string similar to Name can be simplified.. - /// - internal static string NameCanBeSimplified { - get { - return ResourceManager.GetString("NameCanBeSimplified", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Locating the symbol under the caret.... + /// + internal static string LocatingSymbol { + get { + return ResourceManager.GetString("LocatingSymbol", resourceCulture); } - - /// - /// Looks up a localized string similar to Navigate to symbol failed: {0}. - /// - internal static string NavigateToFailed { - get { - return ResourceManager.GetString("NavigateToFailed", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Name can be simplified.. + /// + internal static string NameCanBeSimplified { + get { + return ResourceManager.GetString("NameCanBeSimplified", resourceCulture); } - - /// - /// Looks up a localized string similar to Navigating to symbol.... - /// - internal static string NavigatingTo { - get { - return ResourceManager.GetString("NavigatingTo", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Navigate to symbol failed: {0}. + /// + internal static string NavigateToFailed { + get { + return ResourceManager.GetString("NavigateToFailed", resourceCulture); } - - /// - /// Looks up a localized string similar to Remove unused open declarations. - /// - internal static string RemoveUnusedOpens { - get { - return ResourceManager.GetString("RemoveUnusedOpens", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Navigating to symbol.... + /// + internal static string NavigatingTo { + get { + return ResourceManager.GetString("NavigatingTo", resourceCulture); } - - /// - /// Looks up a localized string similar to Simplify name. - /// - internal static string SimplifyName { - get { - return ResourceManager.GetString("SimplifyName", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Remove unused open declarations. + /// + internal static string RemoveUnusedOpens { + get { + return ResourceManager.GetString("RemoveUnusedOpens", resourceCulture); } - - /// - /// Looks up a localized string similar to The value is unused. - /// - internal static string TheValueIsUnused { - get { - return ResourceManager.GetString("TheValueIsUnused", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Simplify name. + /// + internal static string SimplifyName { + get { + return ResourceManager.GetString("SimplifyName", resourceCulture); } - - /// - /// Looks up a localized string similar to Open declaration can be removed.. - /// - internal static string UnusedOpens { - get { - return ResourceManager.GetString("UnusedOpens", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to The value is unused. + /// + internal static string TheValueIsUnused { + get { + return ResourceManager.GetString("TheValueIsUnused", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open declaration can be removed.. + /// + internal static string UnusedOpens { + get { + return ResourceManager.GetString("UnusedOpens", resourceCulture); } } } From dc012e9f771e0b36731635116431dfd5856f8cab Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 9 Sep 2026 13:12:22 -0700 Subject: [PATCH 5/5] Restore generated language server resources Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 415c03c3-4e89-40bd-89c8-c28b3cab5da8 --- ...anguageServerProtocolResources.Designer.cs | 269 +++++++++--------- 1 file changed, 137 insertions(+), 132 deletions(-) diff --git a/src/LanguageServer/Protocol/LanguageServerProtocolResources.Designer.cs b/src/LanguageServer/Protocol/LanguageServerProtocolResources.Designer.cs index 399308d3fbcc8..813707d88364a 100644 --- a/src/LanguageServer/Protocol/LanguageServerProtocolResources.Designer.cs +++ b/src/LanguageServer/Protocol/LanguageServerProtocolResources.Designer.cs @@ -8,155 +8,160 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.CodeAnalysis.LanguageServer { - using System; +namespace Microsoft.CodeAnalysis.LanguageServer; +using System; + + +/// +/// A strongly-typed resource class, for looking up localized strings, etc. +/// +// This class was auto-generated by the StronglyTypedResourceBuilder +// class via a tool like ResGen or Visual Studio. +// To add or remove a member, edit your .ResX file then rerun ResGen +// with the /str option, or rebuild your VS project. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] +[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +internal sealed class LanguageServerProtocolResources { + + private static global::System.Resources.ResourceManager resourceMan; + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal LanguageServerProtocolResources() { + } /// - /// A strongly-typed resource class, for looking up localized strings, etc. + /// Returns the cached ResourceManager instance used by this class. /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class LanguageServerProtocolResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal LanguageServerProtocolResources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.LanguageServer.LanguageServerProtocolResources", typeof(LanguageServerProtocolResources).Assembly); - resourceMan = temp; - } - return resourceMan; + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.LanguageServer.LanguageServerProtocolResources", typeof(LanguageServerProtocolResources).Assembly); + resourceMan = temp; } + return resourceMan; } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; } - - /// - /// Looks up a localized string similar to Cancelled. - /// - internal static string Cancelled { - get { - return ResourceManager.GetString("Cancelled", resourceCulture); - } + set { + resourceCulture = value; } - - /// - /// Looks up a localized string similar to Unable to deserialize Uri. Unexpected value encountered: {0}. - /// - internal static string DocumentUriSerializationError { - get { - return ResourceManager.GetString("DocumentUriSerializationError", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Cancelled. + /// + internal static string Cancelled { + get { + return ResourceManager.GetString("Cancelled", resourceCulture); } - - /// - /// Looks up a localized string similar to Unable to deserialize FormattingOptions. Invalid token: {0}. - /// - internal static string FormattingOptionsEncounteredInvalidToken { - get { - return ResourceManager.GetString("FormattingOptionsEncounteredInvalidToken", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Unable to deserialize Uri. Unexpected value encountered: {0}. + /// + internal static string DocumentUriSerializationError { + get { + return ResourceManager.GetString("DocumentUriSerializationError", resourceCulture); } - - /// - /// Looks up a localized string similar to Unable to deserialize FormattingOptions as it ended unexpectedly. - /// - internal static string FormattingOptionsEndedUnexpectedly { - get { - return ResourceManager.GetString("FormattingOptionsEndedUnexpectedly", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Unable to deserialize MarkupContent. Unexpected value encountered: {0}. + /// + internal static string MarkupContentSerializationError { + get { + return ResourceManager.GetString("MarkupContentSerializationError", resourceCulture); } - - /// - /// Looks up a localized string similar to Unable to deserialize FormattingOptions. Missing required property: {0}. - /// - internal static string FormattingOptionsMissingRequiredProperty { - get { - return ResourceManager.GetString("FormattingOptionsMissingRequiredProperty", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to A SumType cannot have another SumType as type parameter.. + /// + internal static string NestedSumType { + get { + return ResourceManager.GetString("NestedSumType", resourceCulture); } - - /// - /// Looks up a localized string similar to Unable to deserialize MarkupContent. Unexpected value encountered: {0}. - /// - internal static string MarkupContentSerializationError { - get { - return ResourceManager.GetString("MarkupContentSerializationError", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to None of the SumType type parameters could be deserialized. + /// + internal static string NoSumTypeMatch { + get { + return ResourceManager.GetString("NoSumTypeMatch", resourceCulture); } - - /// - /// Looks up a localized string similar to A SumType cannot have another SumType as type parameter.. - /// - internal static string NestedSumType { - get { - return ResourceManager.GetString("NestedSumType", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Type {0} is missing a contructor that takes a single string as parameter.. + /// + internal static string StringEnumMissingConstructor { + get { + return ResourceManager.GetString("StringEnumMissingConstructor", resourceCulture); } - - /// - /// Looks up a localized string similar to None of the SumType type parameters could be deserialized. - /// - internal static string NoSumTypeMatch { - get { - return ResourceManager.GetString("NoSumTypeMatch", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Unable to deserialize string-based enum. Unexpected data encountered: {0}. + /// + internal static string StringEnumSerializationError { + get { + return ResourceManager.GetString("StringEnumSerializationError", resourceCulture); } - - /// - /// Looks up a localized string similar to Type {0} is missing a contructor that takes a single string as parameter.. - /// - internal static string StringEnumMissingConstructor { - get { - return ResourceManager.GetString("StringEnumMissingConstructor", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0}. + /// + internal static string TextDocumentSyncSerializationError { + get { + return ResourceManager.GetString("TextDocumentSyncSerializationError", resourceCulture); } - - /// - /// Looks up a localized string similar to Unable to deserialize string-based enum. Unexpected data encountered: {0}. - /// - internal static string StringEnumSerializationError { - get { - return ResourceManager.GetString("StringEnumSerializationError", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Unable to deserialize FormattingOptions. Invalid token: {0}. + /// + internal static string FormattingOptionsEncounteredInvalidToken + { + get + { + return ResourceManager.GetString("FormattingOptionsEncounteredInvalidToken", resourceCulture); } - - /// - /// Looks up a localized string similar to Unable to deserialize TextDocumentSyncOptions. Unexpected value encountered: {0}. - /// - internal static string TextDocumentSyncSerializationError { - get { - return ResourceManager.GetString("TextDocumentSyncSerializationError", resourceCulture); - } + } + + /// + /// Looks up a localized string similar to Unable to deserialize FormattingOptions as it ended unexpectedly. + /// + internal static string FormattingOptionsEndedUnexpectedly + { + get + { + return ResourceManager.GetString("FormattingOptionsEndedUnexpectedly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to deserialize FormattingOptions. Missing required property: {0}. + /// + internal static string FormattingOptionsMissingRequiredProperty + { + get + { + return ResourceManager.GetString("FormattingOptionsMissingRequiredProperty", resourceCulture); } } }