From 59381b33198938b14f4469ef27f9d648332be017 Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Thu, 23 Apr 2026 13:52:30 +0200 Subject: [PATCH] Fix GCStats failures on dotnet trace gc-verbose collections (#2414) Two issues prevented the GCStats report from opening for traces collected via `dotnet trace --profile gc-verbose`: 1. `PrintEventCondemnedReasonsTable` in `GcStats.cs` threw NullReferenceException because `GetCondemnedReasonRow` returns null when a TraceGC has neither PerHeapCondemnedReasons nor GlobalCondemnedReasons. Skip such events instead of adding null rows to the table. 2. `AddConcurrentPauseTime` in `TraceManagedProcess.cs` had a `Debug.Assert(_event.PauseDurationMSec == 0)` that was always wrong for BackgroundGCs: the GCStart handler intentionally pre-seeds `PauseDurationMSec = SuspendDurationMSec` for BGCs, so by the time the initial RestartEEStop fires the value is non-zero by design. The else branch's assignment then correctly replaces it with the full pause. Remove the bogus assertion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/PerfView/GcStats.cs | 10 ++++++++-- src/TraceEvent/Computers/TraceManagedProcess.cs | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/PerfView/GcStats.cs b/src/PerfView/GcStats.cs index 9b1c6fa72..81760e22e 100644 --- a/src/PerfView/GcStats.cs +++ b/src/PerfView/GcStats.cs @@ -917,9 +917,15 @@ private static void PrintEventCondemnedReasonsTable(TextWriter writer, TraceProc continue; } } - events.Add(_event); int heapIndexHighestGen; - condemnedReasonRows.Add(GetCondemnedReasonRow(_event, out heapIndexHighestGen)); + byte[] condemnedReasonRow = GetCondemnedReasonRow(_event, out heapIndexHighestGen); + if (condemnedReasonRow == null) + { + // No per-heap or global condemned reasons information available for this event. + continue; + } + events.Add(_event); + condemnedReasonRows.Add(condemnedReasonRow); if (isServerGC) { heapIndexes.Add(heapIndexHighestGen); diff --git a/src/TraceEvent/Computers/TraceManagedProcess.cs b/src/TraceEvent/Computers/TraceManagedProcess.cs index 49f591d1a..d85cadd8c 100644 --- a/src/TraceEvent/Computers/TraceManagedProcess.cs +++ b/src/TraceEvent/Computers/TraceManagedProcess.cs @@ -4773,7 +4773,10 @@ internal void AddConcurrentPauseTime(TraceGC _event, double RestartEEMSec) } else { - Debug.Assert(_event.PauseDurationMSec == 0); + // For a BackgroundGC the initial pause is pre-seeded with SuspendDurationMSec + // at GCStart time (see the BGC branch in the GCStart handler), so PauseDurationMSec + // may already be non-zero when we get here. Overwrite it with the full pause + // (SuspendEE start -> RestartEE end), which is the authoritative value. _event.PauseDurationMSec = RestartEEMSec - _event.PauseStartRelativeMSec; } }