|
<Query Kind="Program"> |
|
<NuGetReference>MeasureIt.exe</NuGetReference> |
|
<NuGetReference>Microsoft.Diagnostics.Tracing.TraceEvent</NuGetReference>a |
|
<NuGetReference>Rx-Core</NuGetReference> |
|
<NuGetReference>Rx-Interfaces</NuGetReference> |
|
<NuGetReference>Rx-Linq</NuGetReference> |
|
<NuGetReference>Rx-Main</NuGetReference> |
|
<Namespace>Microsoft.Diagnostics.Tracing</Namespace> |
|
<Namespace>Microsoft.Diagnostics.Tracing.Parsers</Namespace> |
|
<Namespace>Microsoft.Diagnostics.Tracing.Parsers.Clr</Namespace> |
|
<Namespace>Microsoft.Diagnostics.Tracing.Session</Namespace> |
|
<Namespace>System</Namespace> |
|
<Namespace>System.Collections.Generic</Namespace> |
|
<Namespace>System.Diagnostics</Namespace> |
|
<Namespace>System.Diagnostics.Tracing</Namespace> |
|
<Namespace>System.IO</Namespace> |
|
<Namespace>System.Reactive.Linq</Namespace> |
|
<Namespace>System.Threading</Namespace> |
|
<Namespace>System.Threading.Tasks</Namespace> |
|
<Namespace>PerformanceMeasurement</Namespace> |
|
<Namespace>System.Reactive.Subjects</Namespace> |
|
</Query> |
|
|
|
void Main() |
|
{ |
|
/* |
|
Measure GC Allocations and Duration it take to run a piece of code. |
|
It uses MeasureIt https://www.nuget.org/packages/MeasureIt.exe/ along with |
|
TraceEvent https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/ |
|
for figuring out GC Allocations. |
|
*/ |
|
var useAny = true; |
|
var loremipsum = @"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. |
|
Aenean commodo ligula eget dolor. Aenean massa. |
|
Cum sociis natoque penatibus et magnis dis parturient montes, |
|
nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, |
|
pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla |
|
vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, |
|
venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer |
|
tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend |
|
tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. |
|
Aliquam lorem ante, dapibus in, viverra quis, feugiat a,"; |
|
|
|
var items = loremipsum.Split(new [] {' '}).Distinct().ToList(); |
|
var lookfor = "pede"; |
|
|
|
//Function that needs to be measured. |
|
var runPerf =new Action(() => { |
|
if (!useAny) // Based on the flag run either of the code. |
|
{ |
|
foreach (var item in items) |
|
{ |
|
if (item == lookfor) { |
|
return; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
items.Any (i => i == lookfor); |
|
} |
|
}); |
|
|
|
LinqPadUX.Measure.ActionWithGC(runPerf,500); |
|
} |
|
public static class ObserveGCEvents |
|
{ |
|
// Filter because the traceevent reports for every .NET application running. |
|
const string FilterProcessName = "LINQPad.UserQuery"; |
|
|
|
public static void ActionWithGC(this FluentMeasure measure, |
|
Action runPerf,int counter = 1000) |
|
{ |
|
|
|
if (TraceEventSession.IsElevated() != true) |
|
{ |
|
throw new |
|
Exception(@"You have the run this process as |
|
elevated to capture traces."); |
|
} |
|
|
|
// create a real time user mode session for capturing ETW Traces |
|
var userSession = new TraceEventSession("ObserveGCAllocs"); |
|
|
|
//Subscriber for GC ETW Events |
|
userSession.EnableProvider( |
|
ClrTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, |
|
(ulong)(ClrTraceEventParser.Keywords.GC)); |
|
|
|
//Observable for GC Allocation |
|
IObservable<GCAllocationTickTraceData> gcAllocStream = |
|
userSession.Source.Clr.Observe<GCAllocationTickTraceData>(); |
|
|
|
//Observable for GC Collection |
|
IObservable<GCHeapStatsTraceData> gcCollectStream |
|
= userSession.Source.Clr.Observe<GCHeapStatsTraceData>(); |
|
|
|
var alloc = gcAllocStream |
|
.Select(data => new {ProcessName = GetProcessName(data.ProcessID), |
|
Amount = data.AllocationAmount / 1000, |
|
Type = data.TypeName, |
|
AllocationKind = data.AllocationKind}) |
|
/* Filter*/ .Where(item => item.ProcessName.Contains(FilterProcessName)) |
|
.GroupBy(item => item.Type) |
|
.Select(item => new {Type = item.Key, |
|
AllocationAmount = |
|
item.Select(x => x.Amount).Average() , |
|
Count = item.Count().FirstAsync() |
|
}).Dump("GC Allocation"); |
|
|
|
gcCollectStream.Select(data => |
|
new CollectionStats {ProcessName = GetProcessName(data.ProcessID), |
|
Gen0 = data.GenerationSize0 / 1000000.0, |
|
Gen1 = data.GenerationSize1 / 1000000.0, |
|
Gen2 = data.GenerationSize2 / 1000000.0, |
|
Gen3 = data.GenerationSize3 / 1000000.0, |
|
GCHandleCount = data.GCHandleCount, |
|
PinnedObjectCount = data.PinnedObjectCount}) |
|
.Where(item => item |
|
.ProcessName.Contains(FilterProcessName)) |
|
.Dump("GC Collection Stats"); |
|
|
|
/* Starts Processing ETW Events */ |
|
Task.Run(() => userSession.Source.Process()); |
|
Task.Run( () =>LinqPadUX.Measure |
|
.WithIterationCount(counter) |
|
/* Run's the function */ .Action(runPerf).Dump()) |
|
.ContinueWith((t) => |
|
/* Stops the Trace session */ userSession.Stop()); |
|
|
|
} |
|
private static string GetProcessName(int processID) |
|
{ |
|
// Only keep the cache for 10 seconds to avoid issues with process ID reuse. |
|
var now = DateTime.UtcNow; |
|
if ((now - s_processNameCacheLastUpdate).TotalSeconds > 10) |
|
s_processNameCache.Clear(); |
|
s_processNameCacheLastUpdate = now; |
|
|
|
string ret = null; |
|
if (!s_processNameCache.TryGetValue(processID, out ret)) |
|
{ |
|
Process proc = null; |
|
try { proc = Process.GetProcessById(processID); } |
|
catch (Exception) { } |
|
if (proc != null) |
|
ret = proc.ProcessName; |
|
if (string.IsNullOrWhiteSpace(ret)) |
|
ret = processID.ToString(); |
|
s_processNameCache.Add(processID, ret); |
|
} |
|
return ret; |
|
} |
|
private static Dictionary<int, string> s_processNameCache = |
|
new Dictionary<int, string>(); |
|
private static DateTime s_processNameCacheLastUpdate; |
|
|
|
} |
|
class CollectionStats |
|
{ |
|
public string ProcessName { get; set; } |
|
public double Gen0 { get; set; } |
|
public double Gen1 { get; set; } |
|
public double Gen2 { get; set; } |
|
public double Gen3 { get; set; } |
|
public int GCHandleCount { get; set; } |
|
public int PinnedObjectCount { get; set; } |
|
} |
|
class AllocationData |
|
{ |
|
public string Type { get; set; } |
|
public IObservable<int> ItemSize { get; set; } |
|
public IObservable<int> Count { get; set; } |
|
} |