diff --git a/build/common.props b/build/common.props index 4f167b7377..1fee449923 100644 --- a/build/common.props +++ b/build/common.props @@ -28,7 +28,7 @@ - ..\..\build\strongNameKey.snk + $(MSBuildThisFileDirectory)strongNameKey.snk true true true @@ -73,4 +73,4 @@ all - \ No newline at end of file + diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/BenchmarkDotNet.Diagnostics.Windows.csproj b/src/BenchmarkDotNet.Diagnostics.Windows/BenchmarkDotNet.Diagnostics.Windows.csproj index 7f221554a8..8074793bfc 100644 --- a/src/BenchmarkDotNet.Diagnostics.Windows/BenchmarkDotNet.Diagnostics.Windows.csproj +++ b/src/BenchmarkDotNet.Diagnostics.Windows/BenchmarkDotNet.Diagnostics.Windows.csproj @@ -12,6 +12,6 @@ - + - \ No newline at end of file + diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/EtwProfilerConfig.cs b/src/BenchmarkDotNet.Diagnostics.Windows/EtwProfilerConfig.cs index ee6ed54659..407354f8c3 100644 --- a/src/BenchmarkDotNet.Diagnostics.Windows/EtwProfilerConfig.cs +++ b/src/BenchmarkDotNet.Diagnostics.Windows/EtwProfilerConfig.cs @@ -62,6 +62,7 @@ public EtwProfilerConfig( | ClrTraceEventParser.Keywords.GC | ClrTraceEventParser.Keywords.Jit | ClrTraceEventParser.Keywords.JitTracing // for the inlining events + | ClrTraceEventParser.Keywords.JittedMethodILToNativeMap // Fix NativeMemoryProfiler for .Net Framework | ClrTraceEventParser.Keywords.Loader | ClrTraceEventParser.Keywords.NGen), new TraceEventProviderOptions { StacksEnabled = false }), // stacks are too expensive for our purposes diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/NativeMemoryProfiler.cs b/src/BenchmarkDotNet.Diagnostics.Windows/NativeMemoryProfiler.cs index e80b082ed2..92bc84417c 100644 --- a/src/BenchmarkDotNet.Diagnostics.Windows/NativeMemoryProfiler.cs +++ b/src/BenchmarkDotNet.Diagnostics.Windows/NativeMemoryProfiler.cs @@ -55,7 +55,7 @@ public IEnumerable ProcessResults(DiagnoserResults results) if (!etwProfiler.BenchmarkToEtlFile.TryGetValue(results.BenchmarkCase, out var traceFilePath)) return Enumerable.Empty(); - return new NativeMemoryLogParser(traceFilePath, results.BenchmarkCase, logger).Parse(); + return new NativeMemoryLogParser(traceFilePath, results.BenchmarkCase, logger, results.BuildResult.ArtifactsPaths.ProgramName).Parse(); } public IEnumerable Validate(ValidationParameters validationParameters) => etwProfiler.Validate(validationParameters); @@ -67,7 +67,6 @@ private static EtwProfilerConfig CreateDefaultConfig() var kernelKeywords = KernelTraceEventParser.Keywords.VirtualAlloc | KernelTraceEventParser.Keywords.VAMap; return new EtwProfilerConfig( - providers: Enumerable.Empty<(Guid providerGuid, TraceEventLevel providerLevel, ulong keywords, TraceEventProviderOptions options)>().ToList(), performExtraBenchmarksRun: true, kernelKeywords: kernelKeywords, createHeapSession: true); diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs b/src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs index f50e3b88a1..15629760bc 100644 --- a/src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs +++ b/src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs @@ -22,7 +22,7 @@ public HeapSession(DiagnoserActionParameters details, EtwProfilerConfig config, { } - protected override string FileExtension => ".userheap.etl"; + protected override string FileExtension => "userheap.etl"; internal override Session EnableProviders() { @@ -39,7 +39,7 @@ public UserSession(DiagnoserActionParameters details, EtwProfilerConfig config, { } - protected override string FileExtension => ".etl"; + protected override string FileExtension => "etl"; internal override Session EnableProviders() { @@ -61,7 +61,7 @@ public KernelSession(DiagnoserActionParameters details, EtwProfilerConfig config { } - protected override string FileExtension => ".kernel.etl"; + protected override string FileExtension => "kernel.etl"; internal override Session EnableProviders() { @@ -72,6 +72,8 @@ internal override Session EnableProviders() if (Details.Config.GetHardwareCounters().Any()) keywords |= KernelTraceEventParser.Keywords.PMCProfile; // Precise Machine Counters + TraceEventSession.StackCompression = true; + try { TraceEventSession.EnableKernelProvider(keywords, KernelTraceEventParser.Keywords.Profile); diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/Tracing/NativeMemoryLogParser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/Tracing/NativeMemoryLogParser.cs index cd5c085fc0..ef20f52d39 100644 --- a/src/BenchmarkDotNet.Diagnostics.Windows/Tracing/NativeMemoryLogParser.cs +++ b/src/BenchmarkDotNet.Diagnostics.Windows/Tracing/NativeMemoryLogParser.cs @@ -1,13 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; using Microsoft.Diagnostics.Tracing.Etlx; using Microsoft.Diagnostics.Tracing.Parsers.Kernel; +using Microsoft.Diagnostics.Tracing.Stacks; using Address = System.UInt64; namespace BenchmarkDotNet.Diagnostics.Windows.Tracing @@ -23,11 +26,19 @@ public class NativeMemoryLogParser private readonly ILogger logger; - public NativeMemoryLogParser(string etlFilePath, BenchmarkCase benchmarkCase, ILogger logger) + private readonly string moduleName; + + private readonly string functionName; + + public NativeMemoryLogParser(string etlFilePath, BenchmarkCase benchmarkCase, ILogger logger, + string programName) { this.etlFilePath = etlFilePath; this.benchmarkCase = benchmarkCase; this.logger = logger; + + moduleName = programName; + functionName = nameof(EngineParameters.WorkloadActionUnroll); } //Code is inspired by https://github.com/Microsoft/perfview/blob/master/src/PerfView/PerfViewData.cs#L5719-L5944 @@ -35,6 +46,7 @@ public IEnumerable Parse() { using (var traceLog = new TraceLog(TraceLog.CreateFromEventTraceLogFile(etlFilePath))) { + var stackSource = new MutableTraceEventStackSource(traceLog); var eventSource = traceLog.Events.GetSource(); var bdnEventsParser = new EngineEventLogParser(eventSource); @@ -77,6 +89,14 @@ public IEnumerable Parse() return; } + var call = data.CallStackIndex(); + var frameIndex = stackSource.GetCallStack(call, data); + + if (!IsCallStackIn(frameIndex)) + { + return; + } + var allocs = lastHeapAllocs; if (data.HeapHandle != lastHeapHandle) { @@ -91,7 +111,27 @@ public IEnumerable Parse() nativeLeakSize += data.AllocSize; totalAllocation += data.AllocSize; } + + bool IsCallStackIn(StackSourceCallStackIndex index) + { + while (index != StackSourceCallStackIndex.Invalid) + { + var frame = stackSource.GetFrameIndex(index); + var name = stackSource.GetFrameName(frame, false); + + if (name.StartsWith(moduleName, StringComparison.Ordinal) && + name.IndexOf(functionName, StringComparison.Ordinal) > 0) + { + return true; + } + + index = stackSource.GetCallerIndex(index); + } + + return false; + } }; + heapParser.HeapTraceFree += delegate(HeapFreeTraceData data) { if (!start) @@ -112,6 +152,7 @@ public IEnumerable Parse() allocs.Remove(data.FreeAddress); } }; + heapParser.HeapTraceReAlloc += delegate(HeapReallocTraceData data) { if (!start) @@ -132,22 +173,23 @@ public IEnumerable Parse() allocs = CreateHeapCache(data.HeapHandle, heaps, ref lastHeapAllocs, ref lastHeapHandle); } - // This is a clone of the Free code if (allocs.TryGetValue(data.OldAllocAddress, out long alloc)) { + // Free nativeLeakSize -= alloc; allocs.Remove(data.OldAllocAddress); - } - // This is a clone of the Alloc code (sigh don't clone code) - allocs[data.NewAllocAddress] = data.NewAllocSize; + // Alloc + allocs[data.NewAllocAddress] = data.NewAllocSize; - checked - { - nativeLeakSize += data.NewAllocSize; + checked + { + nativeLeakSize += data.NewAllocSize; + } } }; + heapParser.HeapTraceDestroy += delegate(HeapTraceData data) { if (!start) diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj index 380e2f0c68..4eaa59e97e 100644 --- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj +++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs b/src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs index b4f79ec650..c53d0394a6 100644 --- a/src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs +++ b/src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs @@ -1,16 +1,19 @@ using BenchmarkDotNet.Engines; using BenchmarkDotNet.Running; +using BenchmarkDotNet.Toolchains.Results; namespace BenchmarkDotNet.Diagnosers { public class DiagnoserResults { - public DiagnoserResults(BenchmarkCase benchmarkCase, long totalOperations, GcStats gcStats, ThreadingStats threadingStats) + public DiagnoserResults(BenchmarkCase benchmarkCase, long totalOperations, GcStats gcStats, + ThreadingStats threadingStats, BuildResult buildResult) { BenchmarkCase = benchmarkCase; TotalOperations = totalOperations; GcStats = gcStats; ThreadingStats = threadingStats; + BuildResult = buildResult; } public BenchmarkCase BenchmarkCase { get; } @@ -20,5 +23,7 @@ public DiagnoserResults(BenchmarkCase benchmarkCase, long totalOperations, GcSta public GcStats GcStats { get; } public ThreadingStats ThreadingStats { get; } + + public BuildResult BuildResult { get; } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 61041235d9..66ab60b07d 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -428,7 +428,7 @@ private static (bool success, List executeResults, GcStats gcStat metrics.AddRange( noOverheadCompositeDiagnoser.ProcessResults( - new DiagnoserResults(benchmarkCase, measurements.Where(measurement => measurement.IsWorkload()).Sum(m => m.Operations), gcStats, threadingStats))); + new DiagnoserResults(benchmarkCase, measurements.Where(measurement => measurement.IsWorkload()).Sum(m => m.Operations), gcStats, threadingStats, buildResult))); } if (autoLaunchCount && launchIndex == 2 && analyzeRunToRunVariance) @@ -467,7 +467,7 @@ private static (bool success, List executeResults, GcStats gcStat metrics.AddRange( extraRunCompositeDiagnoser.ProcessResults( - new DiagnoserResults(benchmarkCase, allRuns.Where(measurement => measurement.IsWorkload()).Sum(m => m.Operations), gcStats, threadingStats))); + new DiagnoserResults(benchmarkCase, allRuns.Where(measurement => measurement.IsWorkload()).Sum(m => m.Operations), gcStats, threadingStats, buildResult))); logger.WriteLine(); } diff --git a/tests/BenchmarkDotNet.IntegrationTests/ExporterIOTests.cs b/tests/BenchmarkDotNet.IntegrationTests/ExporterIOTests.cs index a990ba20e1..c2ebe695fb 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/ExporterIOTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/ExporterIOTests.cs @@ -134,7 +134,7 @@ private Summary GetMockSummary(string resultsDirectoryPath, IConfig config, para private class MockExporter : ExporterBase { - public int ExportCount = 0; + public int ExportCount; public override void ExportToLog(Summary summary, ILogger logger) {