Skip to content

Instantly share code, notes, and snippets.

@noahfalk
Created May 19, 2020 06:42
Show Gist options
  • Save noahfalk/85bca5f07fd0e3ce5253211902d48a9d to your computer and use it in GitHub Desktop.
Save noahfalk/85bca5f07fd0e3ce5253211902d48a9d to your computer and use it in GitHub Desktop.
Witnessing the new GC API in the callbacks for EventListenever GC events
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp19
{
/* We could use the new GC API witnessed during EventListener callbacks in attempt to get data from every GC.
* In practice I'd expect this to work most of the time but the events are asynchronous so it is theoretically
* possible that while dispatching a GC event in EventListener another GC has already occured. In this case
* the GC.GetGCInfo() API will be returning information about the most recent GC and not necessarily the GC
* which initially enqueued the event.
*/
class Program
{
static List<object> s_objects = new List<object>();
static void Main(string[] args)
{
GCEventListener listener = new GCEventListener();
Task.Run(AllocateMemory);
Console.ReadLine();
}
static void AllocateMemory()
{
while (true)
{
s_objects.Add(new byte[50_000]);
Thread.Sleep(10);
}
}
}
public class GCEventListener : EventListener
{
enum ClrRuntimeEventKeywords
{
GC = 0x1
}
protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
{
EnableEvents(eventSource, EventLevel.Informational, (EventKeywords)ClrRuntimeEventKeywords.GC);
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
if(eventData.EventName == "GCEnd_V1")
{
OnGCOccured();
}
}
private static void OnGCOccured()
{
GCMemoryInfo memInfo = GC.GetGCInfo();
// add up the GC bytes and fragmentation GC bytes in each generation to get the total for the whole GC
long totalGCBytes = 0;
long totalFragmentationBytes = 0;
foreach (GCGenerationInfo gen in memInfo.GenerationInfo)
{
totalGCBytes += gen.SizeBytes;
totalFragmentationBytes += totalFragmentationBytes;
}
double fragPercentage = totalGCBytes == 0 ? 0 : totalFragmentationBytes * 100.0 / totalGCBytes;
Console.WriteLine($"Last GC fragmentation is {fragPercentage:g2}%");
}
}
#region A non-functional mockup of the in-progress proposed runtime API
public readonly ref struct GCMemoryInfo
{
// Existing properties
public long HighMemoryLoadThresholdBytes { get; }
public long MemoryLoadBytes { get; }
public long TotalAvailableMemoryBytes { get; }
public long HeapSizeBytes { get; }
public long FragmentedBytes { get; }
// New properties
public long Index { get; }
public int Generation { get; }
public bool Compacted { get; }
public bool Concurrent { get; }
public long CommittedBytes { get; }
public long PromotedBytes { get; }
public long PinnedHandlesCount { get; }
public long FinalizationPendingCount { get; }
public ReadOnlySpan<TimeSpan> PauseDurations { get; }
public double PauseTimePercentage { get; }
public ReadOnlySpan<GCGenerationInfo> GenerationInfo { get; }
}
// New type
public readonly struct GCGenerationInfo
{
public long SizeBytes { get; }
public long FragmentationBytes { get; }
}
class GC
{
public static GCMemoryInfo GetGCInfo()
{
return new GCMemoryInfo();
}
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment