Add an EventPipe-backed GC pause duration histogram - #134121
matyaskollert wants to merge 5 commits into
Conversation
Emit typed GC pause contribution events and forward them through EventListener to the System.Runtime histogram. Gate the appended event-sink callback for older standalone EE versions and cover payload accounting, keyword selection, and listener lifecycle. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @steveisok, @dotnet/area-system-diagnostics-tracing |
|
Tagging subscribers to this area: @anicka-net, @dotnet/gc |
Publish the histogram without a private CoreLib capability query and leave it empty when reporting is unavailable. Cover disabled tracing, NativeAOT in-process delivery, and the dispatcher startup race with bounded, isolated tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
I am not yet sure if we require an |
| description: "The total amount of time paused in GC since the process has started."); | ||
|
|
||
| #if NET11_0_OR_GREATER | ||
| InitializeGCPauseMetrics(); |
There was a problem hiding this comment.
We should think about the end-to-end experience and whether this histogram should be part of the default .NET runtime counter set.
Tools like dotnet-counters are going to display this histogram by default since it is in the default set. The histogram can take a lot of space to display. The default .NET runtime counters take ~40 lines in dotnet-counters. The histogram takes another up to 16 lines (that is up 40% more lines).
Should the histogram be in some kind of opt-in counter set?
There was a problem hiding this comment.
some kind of opt-in counter set
Do we have something like this? Would be good inded.
There was a problem hiding this comment.
I do not believe such an opt-in counter set exists yet, but it could be added if filtering using --counters is not sufficient
There was a problem hiding this comment.
For tools like dotnet-counters we would need to explicitly include all instruments except gc pause time, that would be "problematic" since we would need to maintain a separate list in dotnet-counters over what System.Runtime instruments to include by default.
There are also direct consumers of MeterListener (like OpenTelemetry) that would also add all instruments by default for a specific meter.
I believe the question will be, do we want existing System.Runtime subscribers to collect this automatically?
If yes, keep it in System.Runtime and address dotnet-counters presentation (maybe look into an exclude syntax). It will also affect existing usage of MeterListener, like OpenTelemetry, but it should be allowed to add instruments to existing meters, but will increase collection overhead and telemetry volumes if not explicitly filtered.
If no, put it into a different meter collecting more detailed GC instruments like histograms, but it will fragment System.Runtime meter a little, since we now have GC specific instruments scattered across meters, with no initial clear split, some of the GC instruments already in System.Runtime might have been better off put into a detailed meter in the first place.
Add the tracing-disabled event stub and retain the pause keyword during Linux event-state refresh. Yield asynchronously in the in-process test and use a completed background collection with architecture-independent heap sizing for accounting assertions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
In build 1602112, the Windows x86 all-keywords test hit a PGO shutdown assertion. Whether this failure is independent of this PR has not yet been established. |
|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate issues remain in histogram callback synchronization and non-multithreaded dispatcher token capture.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds an EventPipe-backed dotnet.gc.pause.duration histogram for CoreCLR and NativeAOT without introducing a public managed API.
Changes:
- Emits typed
GCPause_V1events from GC pause-accounting paths. - Consumes events through
EventListenerand records histogram measurements. - Adds lifecycle, compatibility, and regression test coverage.
- Captures per-session dispatcher cancellation tokens.
File summaries
| File | Description |
|---|---|
src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Threads.cs |
Captures threaded dispatcher session tokens; the non-multithreaded continuation still needs equivalent capture. |
src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs |
Adds histogram, lifecycle, backlog, and dispatcher regression tests. |
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.GCPause.cs |
Implements EventPipe-backed histogram delivery; callback recording is not synchronized with disposal and re-enablement. |
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs |
Initializes the GC pause histogram. |
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MeterListener.cs |
Notifies instruments of measurement-state changes. |
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs |
Tracks measurement epochs and callbacks. |
src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj |
Includes the new GC pause source file. |
src/coreclr/vm/gctoclreventsink.h |
Declares CoreCLR GC event forwarding. |
src/coreclr/vm/gctoclreventsink.cpp |
Forwards CoreCLR GC pause events. |
src/coreclr/vm/gcenv.ee.cpp |
Enables GC pause-event status tracking. |
src/coreclr/vm/ClrEtwAllMeta.lst |
Configures event metadata. |
src/coreclr/vm/ClrEtwAll.man |
Defines the GC pause event schema and keyword. |
src/coreclr/nativeaot/Runtime/gctoclreventsink.h |
Declares NativeAOT GC event forwarding. |
src/coreclr/nativeaot/Runtime/gctoclreventsink.cpp |
Forwards NativeAOT GC pause events. |
src/coreclr/nativeaot/Runtime/eventpipe/gen-eventing-event-inc.lst |
Includes the generated GC pause event. |
src/coreclr/nativeaot/Runtime/eventpipe/CMakeLists.txt |
Tracks event-generation dependencies. |
src/coreclr/gc/gcpriv.h |
Declares pause recording support. |
src/coreclr/gc/gcinternal.h |
Implements pause-event emission. |
src/coreclr/gc/gcinterface.h |
Updates interface versions and event keywords. |
src/coreclr/gc/gcinterface.ee.h |
Adds the GC event-sink callback. |
src/coreclr/gc/gceventstatus.h |
Guards compatibility with older standalone EEs. |
src/coreclr/gc/gcevents.h |
Registers the GC pause event. |
src/coreclr/gc/env/etmdummy.h |
Adds the no-op event macro. |
src/coreclr/gc/diagnostics.cpp |
Emits background pause contributions. |
src/coreclr/gc/collect.cpp |
Emits blocking pause contributions. |
src/coreclr/gc/background.cpp |
Emits background GC pause contributions. |
Review details
- Files reviewed: 26/26 changed files
- Comments generated: 2
- Review effort level: Lite
| description: "The total amount of time paused in GC since the process has started."); | ||
|
|
||
| #if NET11_0_OR_GREATER | ||
| InitializeGCPauseMetrics(); |
There was a problem hiding this comment.
For tools like dotnet-counters we would need to explicitly include all instruments except gc pause time, that would be "problematic" since we would need to maintain a separate list in dotnet-counters over what System.Runtime instruments to include by default.
There are also direct consumers of MeterListener (like OpenTelemetry) that would also add all instruments by default for a specific meter.
I believe the question will be, do we want existing System.Runtime subscribers to collect this automatically?
If yes, keep it in System.Runtime and address dotnet-counters presentation (maybe look into an exclude syntax). It will also affect existing usage of MeterListener, like OpenTelemetry, but it should be allowed to add instruments to existing meters, but will increase collection overhead and telemetry volumes if not explicitly filtered.
If no, put it into a different meter collecting more detailed GC instruments like histograms, but it will fragment System.Runtime meter a little, since we now have GC specific instruments scattered across meters, with no initial clear split, some of the GC instruments already in System.Runtime might have been better off put into a detailed meter in the first place.
Share callback-gated subscription tracking across target frameworks. Fix nested listener construction and disposal lock ordering, with regression coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Right. There are two parts of this question:
The System.Runtime metrics were intentionally selected as a set that costs next to nothing to collect and that is no-brainer to enable by default and collect all the time. We have shied away from adding histograms to the default set for this reason. Relevant discussion when the System.Runtime metrics were introduced originally - #85372 (comment) . Also, the GC pause times are not the only metric that is a valid candidate for histograms. There are other metrics like thread_pool queue length that have same characteristics as GC pause times that are candidates for histograms. If we start adding histograms to the default set, are we going to add these to the default set as well - what is the total overhead of all histograms going to be by the time we are done? |
|
Agree. If histogram collection has a higher overhead than we want for the always-on baseline, these instruments should probably live in a separate, explicitly selected meter. There are two costs to distinguish here: recording and aggregating the histogram, and transporting the measurements to managed code. Histograms recorded directly from managed code avoid the native-to-managed transport used here. For individual pause measurements emitted from the GC, we need a safe way to buffer and deliver that data to managed consumers. The in-process EventListener approach reuses existing infrastructure and keeps the implementation relatively small, but comes with event transport and dispatch overhead. The original comparison reports a producer-cost difference separately from delivery latency. Before drawing conclusions from those numbers, could we confirm whether the measured implementation included the change disabling stack capture for this event? Commit 1bf894b added that on September 18, after the measurements were posted on September 16. The spaced-collection workload may also exercise buffer allocation more frequently than the steady-state write path: when the reader takes a writable buffer, it clears the producer's write-buffer pointer, so the next event needs a new buffer even if the previous one wasn't full. Separately, the reported ~15 ms delivery latency may largely reflect the dispatcher's explicit If the collection overhead is unacceptable even for opt-in metrics, a more specialized transport may be justified. However, the earlier custom-buffer proposal still needed a managed reader thread to drain the native buffers and record the measurements into the histogram. It also needed synchronization, overflow handling, and lifecycle management, so a custom transport would not eliminate collection overhead or automatically make the histogram suitable for always-on collection. If we invest in a custom transport (or improvements to the existing eventing infrastructure), the collection and dispatch infrastructure should be shared across native runtime instruments and optimized for that purpose. Otherwise, we risk rebuilding parts of the EventListener infrastructure separately for each instrument histogram backed by native code. I would separate the meter-placement decision from the transport decision: both approaches could be reasonable for opt-in detailed metrics without being appropriate for the always-on System.Runtime set. Thoughts? |
|
Thanks for pointing this out—the original measurements predated Average native reporting time per GC pause, on Windows x64 Release:
The gaps are waits inserted by the test, outside the measured reporting call. With no consumer requesting pause events, the path only checks whether the event is enabled; its median cost was below the timer’s 0.1 µs resolution. In the initial GCPerfSim batch, comparing histogram collection disabled versus enabled on the same build:
These are preliminary local measurements, not conclusive overhead estimates. The throughput uncertainty ranges were −10.65% to +0.18% for Workstation and −3.72% to +7.25% for Server. In particular, the positive Server result should not be interpreted as a speed-up. For spaced collections, delivery to the histogram took 14–16 ms median, with p99 around 22–26 ms. This is delayed telemetry, not additional GC pause time. The dispatcher sleep plausibly contributes, but I haven’t isolated its share. The older custom implementation measured 3.9–6.8 µs for spaced reporting calls, but needs a matching rerun before drawing a current comparison. So |
|
@lateralusX @jkotas - would a new opt-in |
|
A few thoughts:
|
|
If we believe that different runtime components might end up with instruments that are not in the default set, then maybe we should place them under their own logical meter namespace, like |
|
@matyaskollert, did we get any matching numbers using the custom transport? |
Appears that go added opt-in GC metrics - open-telemetry/semantic-conventions#3629 Java and V8 appear to have similar histograms by default because they can provide it without too much noise. A separate namespace while retaining the instrument name lines up with the OTel opt-in guidance. open-telemetry/opentelemetry-dotnet-contrib#3977 is a similar request. |
|
@lateralusX I've now run the same benchmarks for the custom queue too: results. When collections were spaced apart, the custom queue took less time to record each pause and get it to the histogram. But when I forced many collections back-to-back in Workstation GC, some pause records did not arrive. In the allocation tests, turning the custom histogram on did not show a clear overall speed difference. These tests were run on different days, so there might be some small variations |



Implements the GC-pause histogram requested in #125753 using EventPipe, as an alternative to #133941. No new public managed API or private CoreLib dependency is introduced.
System.Runtime/dotnet.gc.pause.durationHistogram<double>/sgc.heap.generation:gen0,gen1,gen2;gc.pause.type:blocking,backgroundThe shared GC emits a typed
GCPause_V1event for each existing pause-accounting contribution. DiagnosticSource consumes it through public EventListener APIs and records the duration in seconds. Background contributions remain separate, LOH/POH collections are attributed to Gen 2, and the cumulativedotnet.gc.pause.timecounter is unchanged.The histogram remains available but produces no samples when the runtime cannot provide the events or tracing is disabled. This removes the
GCPauseReporting.IsSupported()helper, reflection/UnsafeAccessor bridge and capability QCall. Delivery remains best-effort; buffer overflow and session changes can lose records, and no reliable dropped-record count is available.Why the dispatcher change is needed
EventPipeEventDispatcher.Threads.csmust capture each session's cancellation token before scheduling its task. Otherwise, a delayed old task can read a newer session's token, continue with the old keyword filter and block the replacement task. Adding the histogram's keyword can then produce no measurements. The included regression test deliberately delays task startup and changes the keyword set; it fails without the fix and passes with it.Validation
Performance
Windows x64 Release GCPerfSim with histogram aggregation enabled (measured revision:
febbedf0bde). Five processes per profile/variant, three-second warmup and at least 15 seconds of measured work, with background GC disabled.Both paired 95% throughput intervals include zero (-2.49% to +5.10%, and -0.72% to +2.19%), so these measurements establish neither a throughput improvement nor a regression. All 50,135 contributions matched native counts and duration totals.
Related to #125753.