Make RoslynTelemetry instance-based with an ambient Current - #85151
Conversation
|
Azure Pipelines: Successfully started running 2 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 415c03c3-4e89-40bd-89c8-c28b3cab5da8
15b1ba6 to
7ea3695
Compare
|
Azure Pipelines: Successfully started running 2 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
It introduces potential perf overhead on hot telemetry no-op paths and leaves some flush sites vulnerable to flushing the wrong instance once ambient telemetry is used.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 3
New issues introduced by this change (4)
| Severity | Finding |
|---|---|
src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs — LanguageServerTelemetry registers event/metric sinks against RoslynTelemetry.Current in… |
|
src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs — AbstractWorkspaceTelemetryService registers sinks against RoslynTelemetry.Current during… |
|
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs — RoslynTelemetry.Current now reads AsyncLocal on every telemetry call (via Logger and direct… |
|
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs — SetCurrent’s doc comment implies any disposal restores the previous instance, but the… |
What changed in this PR
This PR refactors RoslynTelemetry from a static holder of process-wide sink state into an instance-based type with an ambient Current (via AsyncLocal), while keeping Logger’s static API surface intact by forwarding through RoslynTelemetry.Current.
Changes:
- Convert
RoslynTelemetrytointernal sealed partialwith instance sink storage and instance logging/metrics APIs, plus staticCurrent/SetCurrent. - Update
Loggerand selected direct call sites to useRoslynTelemetry.Current.*. - Expand unit coverage to validate ambient flow, nesting, and instance isolation semantics.
| File | Description |
|---|---|
| src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs | Introduces ambient Current/SetCurrent, moves event logging to instance methods, updates test reset semantics. |
| src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_Metrics.cs | Moves metric sinks + metric APIs to instance methods; updates timed metric block to target an instance. |
| src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_LogBlock.cs | Makes log-block factory private and keeps block sink-capture behavior. |
| src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs | Preserves static Logger surface by forwarding calls to RoslynTelemetry.Current. |
| src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteProcessTelemetryService.cs | Registers remote-process event sinks via RoslynTelemetry.Current. |
| src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/PerformanceTrackerService.cs | Routes performance telemetry through RoslynTelemetry.Current. |
| src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs | Routes synchronization counters through RoslynTelemetry.Current. |
| src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs | Adds/updates tests for Current, SetCurrent, async flow, and per-instance behavior. |
| src/Workspaces/Core/Portable/Log/RoslynTelemetry.Workspaces.cs | Moves Workspaces-layer LogBlockTime helper to an instance method. |
| src/Workspaces/Core/Portable/CodeActions/CodeAction.cs | Uses RoslynTelemetry.Current.RecordBlockTime for preview timing. |
| src/VisualStudio/VisualStudioDiagnosticsToolWindow/PerfMargin/PerfMarginPanel.cs | Registers PerfMargin sink via RoslynTelemetry.Current. |
| src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/PerformanceLoggersPage.cs | Registers Performance Logger sinks via RoslynTelemetry.Current. |
| src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ITextViewWindowVerifierInProcessExtensions.cs | Registers integration-test sink via RoslynTelemetry.Current. |
| src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs | Updates test sink registration to RoslynTelemetry.Current. |
| src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests_Telemetry.cs | Updates “anything is listening” check to go through RoslynTelemetry.Current. |
| src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs | Registers sinks and flushes via RoslynTelemetry.Current. |
| src/LanguageServer/Protocol/Handler/Telemetry/RequestTelemetryLogger.cs | Routes LSP request telemetry through RoslynTelemetry.Current. |
| src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/VSCodeRequestTelemetryLogger.cs | Routes VSCode-specific LSP telemetry through RoslynTelemetry.Current. |
| src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs | Registers sinks and flushes via RoslynTelemetry.Current. |
| src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LanguageServerRequestTelemetryTests.cs | Updates metric sink registration to RoslynTelemetry.Current. |
| src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs | Routes diagnostic timing telemetry through RoslynTelemetry.Current. |
| src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_GetDiagnosticsForSpan.cs | Routes span diagnostics timing telemetry through RoslynTelemetry.Current. |
| src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs | Routes refactoring timing telemetry through RoslynTelemetry.Current. |
| src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs | Routes code-fix timing/delay telemetry through RoslynTelemetry.Current. |
| src/EditorFeatures/Core/Suggestions/SuggestedActionsSource_Async.cs | Routes suggested-actions timing telemetry through RoslynTelemetry.Current. |
| src/EditorFeatures/Core/Suggestions/SuggestedActions/EditorSuggestedAction.cs | Routes apply timing telemetry through RoslynTelemetry.Current. |
| src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs | Routes checksum updater counters through RoslynTelemetry.Current. |
| src/EditorFeatures/Core/InlineRename/UI/SmartRename/SmartRenameViewModel_Telemetry.cs | Routes SmartRename telemetry through RoslynTelemetry.Current. |
| /// <summary> | ||
| /// Sets the telemetry instance for the current asynchronous control flow. Disposing the result | ||
| /// restores the previous instance. | ||
| /// </summary> |
| var poster = new RecordingPoster(); | ||
| using var sink = VSMetricSink.TestAccessor.CreateSink(poster); | ||
| using var registration = RoslynTelemetry.AddMetricSink(sink); | ||
| using var registration = RoslynTelemetry.Current.AddMetricSink(sink); |
There was a problem hiding this comment.
Should the tests now just create instances of RoslynTelemetry() so we're not implicitly using static state anymore?
|
outdated |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
ExternalAccessFSharpResources.Designer.cs now has a namespace/ResourceManager base-name mismatch with the project’s embedded resource LogicalName and existing Microsoft.CodeAnalysis.ExternalAccess.FSharp.* consumers, which will break compilation and/or runtime resource lookup.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/VisualStudio/ExternalAccess/Core/FSharp/ExternalAccessFSharpResources.Designer.cs — The generated resources accessor is now in the `Microsoft.VisualStudio.LanguageServices.ExternalAcce… |
Pre-existing issues (1)
| Severity | Finding |
|---|---|
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs — SetCurrent’s doc comment implies any disposal restores the previous instance, but the… View comment |
Issues resolved since last review (3)
| Severity | Finding |
|---|---|
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs — RoslynTelemetry.Current now reads AsyncLocal on every telemetry call (via Logger and direct… View resolved comment |
|
src/VisualStudio/Core/Def/Telemetry/AbstractWorkspaceTelemetryService.cs — AbstractWorkspaceTelemetryService registers sinks against RoslynTelemetry.Current during… View resolved comment |
|
src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Telemetry/LanguageServerTelemetry.cs — LanguageServerTelemetry registers event/metric sinks against RoslynTelemetry.Current in… View resolved comment |
Files not reviewed (2)
- src/LanguageServer/Protocol/LanguageServerProtocolResources.Designer.cs: Generated file
- src/VisualStudio/ExternalAccess/Core/FSharp/ExternalAccessFSharpResources.Designer.cs: Generated file
Suppressed comments (1)
src/VisualStudio/ExternalAccess/Core/FSharp/ExternalAccessFSharpResources.Designer.cs:43
- The ResourceManager base name was changed to
Microsoft.VisualStudio.LanguageServices.ExternalAccess.FSharp.ExternalAccessFSharpResources, but the.csprojexplicitly embeds the.resourcesunderMicrosoft.CodeAnalysis.ExternalAccess.FSharp.ExternalAccessFSharpResources.resources(LogicalName). This mismatch will cause resource lookups to fail at runtime even if compilation succeeds.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 415c03c3-4e89-40bd-89c8-c28b3cab5da8
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Add the RecordBlockTime regression test and remove the unrelated generated-file rewrite.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (2)
| Severity | Finding |
|---|---|
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_Metrics.cs — Please add a regression test for the instance captured by RecordBlockTime: start the scope on one… |
|
src/LanguageServer/Protocol/LanguageServerProtocolResources.Designer.cs — Could we drop this generated-file rewrite from the telemetry refactor?… |
Pre-existing issues (1)
| Severity | Finding |
|---|---|
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs — SetCurrent’s doc comment implies any disposal restores the previous instance, but the… View comment |
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
src/VisualStudio/ExternalAccess/Core/FSharp/ExternalAccessFSharpResources.Designer.cs — The generated resources accessor is now in the `Microsoft.VisualStudio.LanguageServices.ExternalAcce… View resolved comment |
Files not reviewed (1)
- src/LanguageServer/Protocol/LanguageServerProtocolResources.Designer.cs: Generated file
| 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); |
| [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 { |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 415c03c3-4e89-40bd-89c8-c28b3cab5da8
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
One or more issues must be addressed before approval.
Review tier: Lite
Findings: 1
Pre-existing issues (3)
| Severity | Finding |
|---|---|
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry_Metrics.cs — Please add a regression test for the instance captured by RecordBlockTime: start the scope on one… View comment |
|
src/LanguageServer/Protocol/LanguageServerProtocolResources.Designer.cs — Could we drop this generated-file rewrite from the telemetry refactor?… View comment |
|
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/RoslynTelemetry.cs — SetCurrent’s doc comment implies any disposal restores the previous instance, but the… View comment |
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs:60
- This test only proves that two reads are reference-equal; it does not verify that
Currentfell back to the private default instance. It would still pass if an ambient custom instance leaked into the test or ifCurrentreturned another stable singleton. Use the newIsDefaulthelper in the assertion as well.
src/Workspaces/CoreTest/Log/RoslynTelemetryTests.cs:150
- This adds coverage for event blocks capturing their sink list, but the new instance-specific
RecordBlockTimeandLogBlockTimepaths are not tested whenCurrentchanges before disposal. A regression that resolvesCurrentat timed-block disposal would therefore pass the suite; add an analogous two-instance test that starts the scope on the first instance, switches to the second, and verifies the measurement/event is delivered only to the first sink.
public void BlockEndUsesSinksCapturedAtStart()
| ImmutableInterlocked.InterlockedExchange(ref s_metricSinks, []); | ||
| ImmutableInterlocked.InterlockedExchange(ref s_default._eventSinks, []); | ||
| ImmutableInterlocked.InterlockedExchange(ref s_default._metricSinks, []); | ||
| s_current.Value = null; |
There was a problem hiding this comment.
Maybe rename the method to "Reset" since it's doing more than just removing sinks now.



This is PR 1 of 3 and is a pure, behavior-preserving refactor. With nothing calling
SetCurrent, all telemetry resolves to one private default instance, exactly preserving today's semantics.In language-server daemon mode, one process hosts multiple independent LSP servers.
RoslynTelemetrycurrently holds process-wide static sink arrays, so every client's telemetry collapses into oneTelemetrySession. Making telemetry instance-based with an ambientCurrentlays the groundwork for a follow-up to give each logical server its own session.RoslynTelemetryis nowinternal sealed partial, with instance sink arrays and instanceAddEventSink,AddMetricSink,Flush,Log,LogBlock,LogBlockTime,Count,Record, andRecordBlockTimeAPIs.CurrentandSetCurrentremain static, while the default instance is private.Loggerkeeps its exact static surface and forwards toCurrent, leaving the roughly 436Logger.*call sites untouched.Currentreads the ambientAsyncLocaldirectly and falls back to the private default instance. This keeps asynchronous control-flow semantics exact, including child tasks that outlive the scope where they captured the ambient instance.Validation:
dotnet build Compilers.slnf— cleandotnet build Ide.slnf— cleanRoslynTelemetryTests— 15/15 passed on both net10.0 and net472Follow-ups will add per-server telemetry sessions (PR 2) and move the Features telemetry aggregators onto the instance (PR 3).
Microsoft Reviewers: Open in CodeFlow