diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/InliningDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/InliningDiagnoser.cs index 605951f962..4c09957eef 100644 --- a/src/BenchmarkDotNet.Diagnostics.Windows/InliningDiagnoser.cs +++ b/src/BenchmarkDotNet.Diagnostics.Windows/InliningDiagnoser.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; using System.Linq; +using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Running; using Microsoft.Diagnostics.Tracing.Session; namespace BenchmarkDotNet.Diagnostics.Windows { - public class InliningDiagnoser : JitDiagnoser + public class InliningDiagnoser : JitDiagnoser, IProfiler { private static readonly string LogSeparator = new string('-', 20); @@ -43,6 +44,8 @@ public InliningDiagnoser(bool logFailuresOnly = true, string[] allowedNamespaces public override IEnumerable Ids => new[] { nameof(InliningDiagnoser) }; + public string ShortName => "inlining"; + protected override void AttachToEvents(TraceEventSession session, BenchmarkCase benchmarkCase) { defaultNamespace = benchmarkCase.Descriptor.WorkloadMethod.DeclaringType.Namespace; diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs index ddfb63c27f..b189af0acb 100644 --- a/src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs +++ b/src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs @@ -11,7 +11,7 @@ namespace BenchmarkDotNet.Diagnostics.Windows { - public abstract class JitDiagnoser : EtwDiagnoser, IDiagnoser + public abstract class JitDiagnoser : EtwDiagnoser, IDiagnoser where TStats : new() { protected override ulong EventType => (ulong)ClrTraceEventParser.Keywords.JitTracing; diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/JitStatsDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/JitStatsDiagnoser.cs new file mode 100644 index 0000000000..32be37ed9f --- /dev/null +++ b/src/BenchmarkDotNet.Diagnostics.Windows/JitStatsDiagnoser.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Threading; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using Microsoft.Diagnostics.Tracing.Parsers; +using Microsoft.Diagnostics.Tracing.Session; + +namespace BenchmarkDotNet.Diagnostics.Windows +{ + public class JitStatsDiagnoser : JitDiagnoser, IProfiler + { + public override IEnumerable Ids => new[] { nameof(JitStatsDiagnoser) }; + + public string ShortName => "jit"; + + protected override ulong EventType => (ulong)(ClrTraceEventParser.Keywords.Jit | ClrTraceEventParser.Keywords.Compilation); + + public override IEnumerable ProcessResults(DiagnoserResults results) + { + if (BenchmarkToProcess.TryGetValue(results.BenchmarkCase, out int pid)) + { + if (StatsPerProcess.TryGetValue(pid, out JitStats jitStats)) + { + yield return new Metric(MethodsJittedDescriptor.Instance, jitStats.MethodsCompiled); + yield return new Metric(MethodsTieredDescriptor.Instance, jitStats.MethodsTiered); + yield return new Metric(JitAllocatedMemoryDescriptor.Instance, jitStats.MemoryAllocated); + } + } + } + + protected override void AttachToEvents(TraceEventSession session, BenchmarkCase benchmarkCase) + { + session.Source.Clr.MethodJittingStarted += methodData => + { + if (StatsPerProcess.TryGetValue(methodData.ProcessID, out JitStats jitStats)) + { + Interlocked.Increment(ref jitStats.MethodsCompiled); + } + }; + + session.Source.Clr.MethodMemoryAllocatedForJitCode += memoryAllocated => + { + if (StatsPerProcess.TryGetValue(memoryAllocated.ProcessID, out JitStats jitStats)) + { + Interlocked.Add(ref jitStats.MemoryAllocated, memoryAllocated.AllocatedSizeForJitCode); + } + }; + + session.Source.Clr.TieredCompilationBackgroundJitStart += tieredData => + { + if (StatsPerProcess.TryGetValue(tieredData.ProcessID, out JitStats jitStats)) + { + Interlocked.Increment(ref jitStats.MethodsTiered); + } + }; + } + + private sealed class MethodsJittedDescriptor : IMetricDescriptor + { + internal static readonly MethodsJittedDescriptor Instance = new (); + + public string Id => nameof(MethodsJittedDescriptor); + public string DisplayName => "Methods JITted"; + public string Legend => "Total number of methods JITted during entire benchmark execution (including warmup)."; + public bool TheGreaterTheBetter => false; + public string NumberFormat => "N0"; + public UnitType UnitType => UnitType.Dimensionless; + public string Unit => "Count"; + public int PriorityInCategory => 0; + } + + private sealed class MethodsTieredDescriptor : IMetricDescriptor + { + internal static readonly MethodsTieredDescriptor Instance = new (); + + public string Id => nameof(MethodsTieredDescriptor); + public string DisplayName => "Methods Tiered"; + public string Legend => "Total number of methods re-compiled by Tiered JIT during entire benchmark execution (including warmup)."; + public bool TheGreaterTheBetter => false; + public string NumberFormat => "N0"; + public UnitType UnitType => UnitType.Dimensionless; + public string Unit => "Count"; + public int PriorityInCategory => 0; + } + + private sealed class JitAllocatedMemoryDescriptor : IMetricDescriptor + { + internal static readonly JitAllocatedMemoryDescriptor Instance = new (); + + public string Id => nameof(JitAllocatedMemoryDescriptor); + public string DisplayName => "JIT allocated memory"; + public string Legend => "Total memory allocated by the JIT during entire benchmark execution (including warmup)."; + public bool TheGreaterTheBetter => false; + public string NumberFormat => "N0"; + public UnitType UnitType => UnitType.Size; + public string Unit => SizeUnit.B.Name; + public int PriorityInCategory => 0; + } + } + + public sealed class JitStats + { + public long MethodsCompiled; + public long MethodsTiered; + public long MemoryAllocated; + } +} diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/TailCallDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/TailCallDiagnoser.cs index 74227fc990..8c76337d20 100644 --- a/src/BenchmarkDotNet.Diagnostics.Windows/TailCallDiagnoser.cs +++ b/src/BenchmarkDotNet.Diagnostics.Windows/TailCallDiagnoser.cs @@ -2,6 +2,7 @@ using BenchmarkDotNet.Running; using Microsoft.Diagnostics.Tracing.Session; using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Diagnosers; namespace BenchmarkDotNet.Diagnostics.Windows { @@ -9,7 +10,7 @@ namespace BenchmarkDotNet.Diagnostics.Windows /// See MSDN blog post about JIT tracing events /// and detailed blog post by George Plotnikov for more info /// - public class TailCallDiagnoser : JitDiagnoser + public class TailCallDiagnoser : JitDiagnoser, IProfiler { private static readonly string LogSeparator = new string('-', 20); @@ -32,6 +33,8 @@ public TailCallDiagnoser(bool logFailuresOnly = true, bool filterByNamespace = t public override IEnumerable Ids => new[] { nameof(TailCallDiagnoser) }; + public string ShortName => "tail"; + protected override void AttachToEvents(TraceEventSession traceEventSession, BenchmarkCase benchmarkCase) { expectedNamespace = benchmarkCase.Descriptor.WorkloadMethod.DeclaringType.Namespace ?? benchmarkCase.Descriptor.WorkloadMethod.DeclaringType.FullName; diff --git a/src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs b/src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs index 57bb5ee30e..1410aad8bc 100644 --- a/src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs +++ b/src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs @@ -73,6 +73,8 @@ private static IDiagnoser[] LoadWindowsDiagnosers() return new[] { CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser"), + CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.TailCallDiagnoser"), + CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.JitStatsDiagnoser"), CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.EtwProfiler"), CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.ConcurrencyVisualizerProfiler"), CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.NativeMemoryProfiler")