Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Kayle Hinkle committed Feb 1, 2019
1 parent ef090df commit 7d4a237
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 34 deletions.
149 changes: 149 additions & 0 deletions src/BenchmarkDotNet.Diagnostics.Windows/DetailedMemoryDiagnoser.cs
@@ -0,0 +1,149 @@
using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Diagnostics.Windows.Tracing;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BenchmarkDotNet.Diagnosers
{
public class DetailedMemoryDiagnoser : EtwDiagnoserBase, IDiagnoser
{
private const string DiagnoserId = nameof(DetailedMemoryDiagnoser);

public static readonly DetailedMemoryDiagnoser Default = new DetailedMemoryDiagnoser();

private readonly Dictionary<BenchmarkCase, AllocationTracker> results = new Dictionary<BenchmarkCase, AllocationTracker>();

public IEnumerable<string> Ids => new[] { DiagnoserId };

protected override ulong EventType => (ulong)(
ClrTraceEventParser.Keywords.GCAllObjectAllocation |
ClrTraceEventParser.Keywords.GCHeapAndTypeNames |
ClrTraceEventParser.Keywords.Type);

protected override string SessionNamePrefix => "Memory";

public void DisplayResults(ILogger logger)
{
logger.WriteLine("DetailedMemoryDiagnoser results:");
foreach (var benchmark in results)
{
logger.WriteLine($"{benchmark.Key.DisplayInfo} allocations:");
foreach (var item in benchmark.Value.GetAllocations())
{
logger.WriteLine($"{item.name}, {item.size}, {item.count}");
}
}

var x = new StringBuilder();
foreach (var item in Logger.CapturedOutput)
{
x.Append(item.Text);
}
}

public bool isActive = false;

public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
{
switch (signal)
{
case HostSignal.BeforeProcessStart:
Start(parameters); // GCAllObjectAllocation must be enabled before process starts
break;
case HostSignal.BeforeAnythingElse:
Session.DisableProvider(ClrTraceEventParser.ProviderGuid); // it can be disabled until the benchmark runs.
break;
case HostSignal.BeforeActualRun:
EnableProvider(parameters); // Re-enable allocation tracking now
break;
case HostSignal.AfterActualRun:
Stop();
break;
case HostSignal.AfterAll:
break;
case HostSignal.SeparateLogic:
break;
case HostSignal.AfterProcessExit:
break;
default:
break;
}
}

public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => Array.Empty<Metric>();
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) => Array.Empty<ValidationError>();

protected override void AttachToEvents(TraceEventSession session, BenchmarkCase benchmarkCase)
{
session.EnableKernelProvider(KernelTraceEventParser.Keywords.ImageLoad);

var tracker = results[benchmarkCase] = new AllocationTracker();
Logger.WriteLine();
Logger.WriteLineInfo($"{benchmarkCase.DisplayInfo}");
var relogger = new ETWReloggerTraceEventSource(session.SessionName, TraceEventSourceType.Session, @"C:\temp\output.etl");
relogger.AllEvents += x => relogger.WriteEvent(x);

Task.Run(() => relogger.Process());
session.EnableProvider(EngineEventSource.Log.Name, TraceEventLevel.Informational);

var bdnParser = new EngineEventLogParser(session.Source);
bdnParser.WorkloadActualStart += _ => { isActive = true; };
bdnParser.WorkloadActualStop += _ => { isActive = false; }; // stop tracking allocations from outside the benchmark

session.Source.Clr.GCSampledObjectAllocation += x =>
{
if (isActive) tracker.Add(x.TypeID, (ulong)x.TotalSizeForTypeSample);
};

session.Source.Clr.TypeBulkType += data =>
{
for (int i = 0; i < data.Count; i++)
{
tracker.AddTypeName(data.Values(i).TypeID, data.Values(i).TypeName);
}
};
}

private class AllocationTracker
{
private readonly Dictionary<ulong, string> typeNames = new Dictionary<ulong, string>();
private Dictionary<ulong, (int count, ulong size)> counters = new Dictionary<ulong, (int count, ulong size)>();

internal void Add(ulong typeID, ulong totalSizeForTypeSample)
{
if (!counters.TryGetValue(typeID, out var value))
{
value = (0, 0);
}

var (count, size) = value;
counters[typeID] = (count + 1, size + totalSizeForTypeSample);
}

internal void AddTypeName(ulong typeID, string typeName)
{
typeNames[typeID] = typeName;
}

internal IEnumerable<(string name, int count, ulong size)> GetAllocations()
{
foreach (var item in counters.OrderByDescending(x => x.Value.size))
{
yield return (typeNames[item.Key], item.Value.count, item.Value.size);
}
}
}
}
}
81 changes: 48 additions & 33 deletions src/BenchmarkDotNet.Diagnostics.Windows/EtwDiagnoser.cs
@@ -1,27 +1,25 @@
using System;
using System.Collections.Concurrent;
using BenchmarkDotNet.Parameters;
using BenchmarkDotNet.Running;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Parameters;
using BenchmarkDotNet.Running;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace BenchmarkDotNet.Diagnostics.Windows
{
public abstract class EtwDiagnoser<TStats> where TStats : new()

public abstract class EtwDiagnoserBase
{
internal readonly LogCapture Logger = new LogCapture();
protected readonly Dictionary<BenchmarkCase, int> BenchmarkToProcess = new Dictionary<BenchmarkCase, int>();
protected readonly ConcurrentDictionary<int, TStats> StatsPerProcess = new ConcurrentDictionary<int, TStats>();

public virtual RunMode GetRunMode(BenchmarkCase benchmarkCase) => RunMode.ExtraRun;

public virtual IEnumerable<IExporter> Exporters => Array.Empty<IExporter>();
public virtual IEnumerable<IAnalyser> Analysers => Array.Empty<IAnalyser>();

Expand All @@ -31,66 +29,59 @@ public abstract class EtwDiagnoser<TStats> where TStats : new()

protected abstract string SessionNamePrefix { get; }

protected void Start(DiagnoserActionParameters parameters)
protected virtual void Start(DiagnoserActionParameters parameters)
{
Clear();

BenchmarkToProcess.Add(parameters.BenchmarkCase, parameters.Process.Id);
StatsPerProcess.TryAdd(parameters.Process.Id, GetInitializedStats(parameters));

Session = CreateSession(parameters.BenchmarkCase);

Console.CancelKeyPress += OnConsoleCancelKeyPress;

NativeWindowsConsoleHelper.OnExit += OnConsoleCancelKeyPress;

EnableProvider();

AttachToEvents(Session, parameters.BenchmarkCase);

EnableProvider(parameters);

// The ETW collection thread starts receiving events immediately, but we only
// start aggregating them after ProcessStarted is called and we know which process
// (or processes) we should be monitoring. Communication between the benchmark thread
// and the ETW collection thread is through the statsPerProcess concurrent dictionary
// and through the TraceEventSession class, which is thread-safe.
var task = Task.Factory.StartNew((Action)(() => Session.Source.Process()), TaskCreationOptions.LongRunning);
var task = Task.Factory.StartNew(() => Session.Source.Process(), TaskCreationOptions.LongRunning);

// wait until the processing has started, block by then so we don't loose any
// information (very important for jit-related things)
WaitUntilStarted(task);
}

protected virtual TStats GetInitializedStats(DiagnoserActionParameters parameters) => new TStats();

protected virtual TraceEventSession CreateSession(BenchmarkCase benchmarkCase)
=> new TraceEventSession(GetSessionName(SessionNamePrefix, benchmarkCase, benchmarkCase.Parameters));

protected virtual void EnableProvider()
protected virtual void EnableProvider(DiagnoserActionParameters parameters = null)
{
Session.EnableProvider(
ClrTraceEventParser.ProviderGuid,
TraceEventLevel.Verbose,
EventType);
EventType,
new TraceEventProviderOptions
{
StacksEnabled = true,
ProcessNameFilter = parameters != null
? new List<string> { Path.GetFileName(parameters?.Process.StartInfo.FileName) }
: null
});
}

protected abstract void AttachToEvents(TraceEventSession traceEventSession, BenchmarkCase benchmarkCase);

protected void Stop()
{
WaitForDelayedEvents();

Session.Dispose();

Console.CancelKeyPress -= OnConsoleCancelKeyPress;
NativeWindowsConsoleHelper.OnExit -= OnConsoleCancelKeyPress;
}

private void Clear()
{
BenchmarkToProcess.Clear();
StatsPerProcess.Clear();
}

private void OnConsoleCancelKeyPress(object sender, ConsoleCancelEventArgs e) => Session?.Dispose();

private static string GetSessionName(string prefix, BenchmarkCase benchmarkCase, ParameterInstances parameters = null)
Expand Down Expand Up @@ -121,4 +112,28 @@ private static void WaitForDelayedEvents()
Thread.Sleep(TimeSpan.FromSeconds(3));
}
}

public abstract class EtwDiagnoser<TStats> : EtwDiagnoserBase where TStats : new()
{
protected readonly Dictionary<BenchmarkCase, int> BenchmarkToProcess = new Dictionary<BenchmarkCase, int>();
protected readonly ConcurrentDictionary<int, TStats> StatsPerProcess = new ConcurrentDictionary<int, TStats>();

protected override void Start(DiagnoserActionParameters parameters)
{
Clear();

BenchmarkToProcess.Add(parameters.BenchmarkCase, parameters.Process.Id);
StatsPerProcess.TryAdd(parameters.Process.Id, GetInitializedStats(parameters));

base.Start(parameters);
}

protected virtual TStats GetInitializedStats(DiagnoserActionParameters parameters) => new TStats();

private void Clear()
{
BenchmarkToProcess.Clear();
StatsPerProcess.Clear();
}
}
}
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet.Diagnostics.Windows/PmcDiagnoser.cs
Expand Up @@ -71,7 +71,7 @@ protected override PmcStats GetInitializedStats(DiagnoserActionParameters parame
protected override TraceEventSession CreateSession(BenchmarkCase benchmarkCase)
=> new TraceEventSession(KernelTraceEventParser.KernelSessionName);

protected override void EnableProvider()
protected override void EnableProvider(DiagnoserActionParameters parameters)
=> Session.EnableKernelProvider((KernelTraceEventParser.Keywords)EventType);

protected override void AttachToEvents(TraceEventSession traceEventSession, BenchmarkCase benchmarkCase)
Expand Down

0 comments on commit 7d4a237

Please sign in to comment.