Created
May 19, 2020 06:42
-
-
Save noahfalk/85bca5f07fd0e3ce5253211902d48a9d to your computer and use it in GitHub Desktop.
Witnessing the new GC API in the callbacks for EventListenever GC events
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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