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..78aeb53f7ca78 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,9 @@ public async Task RealRequestsProduceAggregatedTelemetry() { var poster = new RecordingPoster(); using var sink = VSMetricSink.TestAccessor.CreateSink(poster); - using var registration = RoslynTelemetry.AddMetricSink(sink); + + using var telemetry = RoslynTelemetry.SetCurrent(new RoslynTelemetry()); + 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..7313124694ab7 100644 --- a/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs +++ b/src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs @@ -32,10 +32,11 @@ 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.AddEventSink), - RoslynTelemetry.AddMetricSink(metricSink), + .. CreateEventSinks(telemetrySession, logDelta).SelectAsArray(RoslynTelemetry.Current.AddEventSink), + RoslynTelemetry.Current.AddMetricSink(metricSink), metricSink, ]; @@ -70,7 +71,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/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/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..7a30a6bd65003 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,132 @@ 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(); + + Assert.Same(defaultTelemetry, RoslynTelemetry.Current); + using (RoslynTelemetry.SetCurrent(firstTelemetry)) + { + Assert.Same(firstTelemetry, RoslynTelemetry.Current); + + using (RoslynTelemetry.SetCurrent(secondTelemetry)) + { + Assert.Same(secondTelemetry, RoslynTelemetry.Current); + } + + Assert.Same(firstTelemetry, RoslynTelemetry.Current); + } + + Assert.Same(defaultTelemetry, RoslynTelemetry.Current); + } + + [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 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() + { + 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 +188,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 +204,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 +219,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 +234,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 +258,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 +291,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 521e203183ae6..4569cfa67e8b6 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. 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..b6a4ac97d2769 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs @@ -14,27 +14,48 @@ 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(); + /// /// 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 + => 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; + s_current.Value = 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 /// 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 +80,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 +105,20 @@ 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; } } /// /// 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 +133,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 +148,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 +162,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 +176,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 +190,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 +203,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 +224,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 +239,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 +248,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 +257,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 +266,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 +275,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 +284,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); } } }