// This event is used to figure out if a collection is compacting or not
        // Note: last event for background GC (will be GCHeapStats for ephemeral (0/1) and non concurrent gen 2 collections)
        private void OnGcGlobalHeapHistory(EventWrittenEventArgs e)
        {
            var currentGC = GetCurrentGC(_gcInfo);

            // check unexpected event (we should have received a GCStart first)
            if (currentGC == null)
            {
                return;
            }

            // check if the collection was compacting
            var globalMask = (GCGlobalMechanisms)GetFieldValue <uint>(e, "GlobalMechanisms");

            currentGC.IsCompacting =
                (globalMask & GCGlobalMechanisms.Compaction) == GCGlobalMechanisms.Compaction;

            // this is the last event for gen 2 background collections
            if ((GetFieldValue <uint>(e, "CondemnedGeneration") == 2) && (currentGC.Type == GCType.BackgroundGC))
            {
                // check unexpected generation mismatch: should never occur
                if (currentGC.Generation != GetFieldValue <uint>(e, "CondemnedGeneration"))
                {
                    return;
                }

                GcEvents?.Invoke(this, BuildGcArgs(currentGC));
                ClearCollections(_gcInfo);
            }
        }
        // This event provides the size of each generation after the collection
        // Note: last event for non background GC (will be GcGlobalHeapHistory for background gen 2)
        private void OnGcHeapStats(EventWrittenEventArgs e)
        {
            var currentGC = GetCurrentGC(_gcInfo);

            if (currentGC == null)
            {
                return;
            }

            currentGC.Heaps.Gen0Size = (long)GetFieldValue <ulong>(e, "GenerationSize0");
            currentGC.Heaps.Gen1Size = (long)GetFieldValue <ulong>(e, "GenerationSize1");
            currentGC.Heaps.Gen2Size = (long)GetFieldValue <ulong>(e, "GenerationSize2");
            currentGC.Heaps.LOHSize  = (long)GetFieldValue <ulong>(e, "GenerationSize3");

            // this is the last event for a gen0/gen1 foreground collection during a background gen2 collections
            if (
                (_gcInfo.CurrentBGC != null) &&
                (currentGC.Generation < 2)
                )
            {
                Debug.Assert(_gcInfo.GCInProgress == currentGC);

                GcEvents?.Invoke(this, BuildGcArgs(currentGC));
                _gcInfo.GCInProgress = null;
            }
        }
        private void OnGcRestartEEEnd(EventWrittenEventArgs e)
        {
            var currentGC = GetCurrentGC(_gcInfo);

            if (currentGC == null)
            {
                // this should never happen, except if we are unlucky to have missed a GCStart event
                return;
            }

            // compute suspension time
            double suspensionDuration = 0;

            if (_gcInfo.SuspensionStart.HasValue)
            {
                suspensionDuration      = (e.TimeStamp - _gcInfo.SuspensionStart.Value).TotalMilliseconds;
                _gcInfo.SuspensionStart = null;
            }
            else
            {
                // bad luck: a xxxBegin event has been missed
            }
            currentGC.PauseDuration += suspensionDuration;

            // could be the end of a gen0/gen1 or of a non concurrent gen2 GC
            if (
                (currentGC.Generation < 2) ||
                (currentGC.Type == GCType.NonConcurrentGC)
                )
            {
                GcEvents?.Invoke(this, BuildGcArgs(currentGC));
                _gcInfo.GCInProgress = null;
                return;
            }

            // in case of background gen2, just need to sum the suspension time
            // --> its end will be detected during GcGlobalHistory event
        }