Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The EventPipeProfiler cross-platform profiler #1321

Merged
merged 14 commits into from
Mar 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions BenchmarkDotNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Diagnostics
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.IntegrationTests.CustomPaths", "tests\BenchmarkDotNet.IntegrationTests.CustomPaths\BenchmarkDotNet.IntegrationTests.CustomPaths.csproj", "{0031728E-A5D4-47C1-9C1A-6C859A765C9D}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BenchmarkDotNet.Samples.FSharp", "samples\BenchmarkDotNet.Samples.FSharp\BenchmarkDotNet.Samples.FSharp.fsproj", "{A329F00E-4B9D-4BC6-B688-92698D773CBF}"
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "BenchmarkDotNet.Samples.FSharp", "samples\BenchmarkDotNet.Samples.FSharp\BenchmarkDotNet.Samples.FSharp.fsproj", "{A329F00E-4B9D-4BC6-B688-92698D773CBF}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BenchmarkDotNet.IntegrationTests.FSharp", "tests\BenchmarkDotNet.IntegrationTests.FSharp\BenchmarkDotNet.IntegrationTests.FSharp.fsproj", "{367FAFE1-A1C8-4AA1-9334-F4762E128DBB}"
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "BenchmarkDotNet.IntegrationTests.FSharp", "tests\BenchmarkDotNet.IntegrationTests.FSharp\BenchmarkDotNet.IntegrationTests.FSharp.fsproj", "{367FAFE1-A1C8-4AA1-9334-F4762E128DBB}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "BenchmarkDotNet.IntegrationTests.VisualBasic", "tests\BenchmarkDotNet.IntegrationTests.VisualBasic\BenchmarkDotNet.IntegrationTests.VisualBasic.vbproj", "{D8803ECA-4ABF-45B8-BD1D-7C637416C448}"
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "BenchmarkDotNet.IntegrationTests.VisualBasic", "tests\BenchmarkDotNet.IntegrationTests.VisualBasic\BenchmarkDotNet.IntegrationTests.VisualBasic.vbproj", "{D8803ECA-4ABF-45B8-BD1D-7C637416C448}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.IntegrationTests.DisabledOptimizations", "tests\BenchmarkDotNet.IntegrationTests.DisabledOptimizations\BenchmarkDotNet.IntegrationTests.DisabledOptimizations.csproj", "{AC2188E5-A140-43E2-8A76-4BDABFE30ABA}"
EndProject
Expand All @@ -37,17 +37,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Disassemble
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Disassembler.x86", "src\BenchmarkDotNet.Disassembler.x86\BenchmarkDotNet.Disassembler.x86.csproj", "{D189AAB3-46B4-4437-8E9C-72F021AB2B6E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.IntegrationTests.ManualRunning", "tests\BenchmarkDotNet.IntegrationTests.ManualRunning\BenchmarkDotNet.IntegrationTests.ManualRunning.csproj", "{9816D316-95C4-42E6-9E7B-A256C7E5D4BF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.IntegrationTests.ManualRunning", "tests\BenchmarkDotNet.IntegrationTests.ManualRunning\BenchmarkDotNet.IntegrationTests.ManualRunning.csproj", "{9816D316-95C4-42E6-9E7B-A256C7E5D4BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Tool", "src\BenchmarkDotNet.Tool\BenchmarkDotNet.Tool.csproj", "{1336CAFF-5321-495E-BAFA-AB4DC6BF791C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Tool", "src\BenchmarkDotNet.Tool\BenchmarkDotNet.Tool.csproj", "{1336CAFF-5321-495E-BAFA-AB4DC6BF791C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.IntegrationTests.Static", "tests\BenchmarkDotNet.IntegrationTests.Static\BenchmarkDotNet.IntegrationTests.Static.csproj", "{B4405781-40D3-42B8-B168-00E711FABA15}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.IntegrationTests.Static", "tests\BenchmarkDotNet.IntegrationTests.Static\BenchmarkDotNet.IntegrationTests.Static.csproj", "{B4405781-40D3-42B8-B168-00E711FABA15}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Annotations", "src\BenchmarkDotNet.Annotations\BenchmarkDotNet.Annotations.csproj", "{D9F5065B-6190-431B-850C-117E3D64AB33}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Annotations", "src\BenchmarkDotNet.Annotations\BenchmarkDotNet.Annotations.csproj", "{D9F5065B-6190-431B-850C-117E3D64AB33}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{63B94FD6-3F3D-4E04-9727-48E86AC4384C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Templates", "templates\BenchmarkDotNet.Templates.csproj", "{B620D10A-CD8E-4A34-8B27-FD6257E63AD0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Templates", "templates\BenchmarkDotNet.Templates.csproj", "{B620D10A-CD8E-4A34-8B27-FD6257E63AD0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
2 changes: 1 addition & 1 deletion build/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</PropertyGroup>

<PropertyGroup Condition=" '$(IsVisualBasic)' != 'true' AND '$(IsFsharp)' != 'true' ">
<LangVersion>7.2</LangVersion>
<LangVersion>7.3</LangVersion>

<Major>0</Major>
<Minor>12</Minor>
Expand Down
14 changes: 14 additions & 0 deletions samples/BenchmarkDotNet.Samples/IntroEventPipeProfiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;

namespace BenchmarkDotNet.Samples
{
[ShortRunJob]
[EventPipeProfiler(EventPipeProfile.CpuSampling)]
public class IntroEventPipeProfiler
{
[Benchmark]
public void Sleep() => Thread.Sleep(2000);
}
}
46 changes: 46 additions & 0 deletions samples/BenchmarkDotNet.Samples/IntroEventPipeProfilerAdvance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using System.Buffers;
using System.Diagnostics.Tracing;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing.Parsers;

namespace BenchmarkDotNet.Samples
{
[Config(typeof(CustomConfig))]
public class IntroEventPipeProfilerAdvance
{
private class CustomConfig : ManualConfig
{
public CustomConfig()
{
AddJob(Job.ShortRun.WithRuntime(CoreRuntime.Core30));

var providers = new[]
{
new EventPipeProvider(ClrTraceEventParser.ProviderName, EventLevel.Verbose, (long) (ClrTraceEventParser.Keywords.Exception
| ClrTraceEventParser.Keywords.GC
| ClrTraceEventParser.Keywords.Jit
| ClrTraceEventParser.Keywords.JitTracing // for the inlining events
| ClrTraceEventParser.Keywords.Loader
| ClrTraceEventParser.Keywords.NGen)),
new EventPipeProvider("System.Buffers.ArrayPoolEventSource", EventLevel.Informational, Int64.MaxValue),
};

AddDiagnoser(new EventPipeProfiler(providers: providers));
}
}

[Benchmark]
public void RentAndReturn_Shared()
{
var pool = ArrayPool<byte>.Shared;
byte[] array = pool.Rent(10000);
pool.Return(array);
}
}
}
1 change: 0 additions & 1 deletion samples/BenchmarkDotNet.Samples/IntroNativeMemory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
Expand Down
1 change: 1 addition & 0 deletions src/BenchmarkDotNet.Diagnostics.Windows/EtwDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
Expand Down
32 changes: 3 additions & 29 deletions src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains;
using BenchmarkDotNet.Extensions;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
Expand Down Expand Up @@ -104,8 +103,8 @@ protected Session(string sessionName, DiagnoserActionParameters details, EtwProf
{
Details = details;
Config = config;
FilePath = EnsureFolderExists(GetFilePath(details, creationTime));

FilePath = ArtifactFileNameHelper.GetFilePath(details, creationTime, FileExtension);
Path.GetDirectoryName(FilePath).CreateIfNotExists();
TraceEventSession = new TraceEventSession(sessionName, FilePath)
{
BufferSizeMB = config.BufferSizeInMb,
Expand All @@ -131,30 +130,5 @@ internal void Stop()
private void OnConsoleCancelKeyPress(object sender, ConsoleCancelEventArgs e) => Stop();

private void OnProcessExit(object sender, EventArgs e) => Stop();

private string GetFilePath(DiagnoserActionParameters details, DateTime creationTime)
{
string fileName = $@"{FolderNameHelper.ToFolderName(details.BenchmarkCase.Descriptor.Type)}.{FullNameProvider.GetMethodName(details.BenchmarkCase)}";

// if we run for more than one toolchain, the output file name should contain the name too so we can differ net461 vs netcoreapp2.1 etc
if (details.Config.GetJobs().Select(job => job.GetToolchain()).Distinct().Count() > 1)
fileName += $"-{details.BenchmarkCase.Job.Environment.Runtime?.Name ?? details.BenchmarkCase.GetToolchain()?.Name ?? details.BenchmarkCase.Job.Id}";

fileName += $"-{creationTime.ToString(BenchmarkRunnerClean.DateTimeFormat)}";

fileName = FolderNameHelper.ToFolderName(fileName);

return Path.Combine(details.Config.ArtifactsPath, $"{fileName}{FileExtension}");
}

private string EnsureFolderExists(string filePath)
{
string directoryPath = Path.GetDirectoryName(filePath);

if (!Directory.Exists(directoryPath))
Directory.CreateDirectory(directoryPath);

return filePath;
}
}
}
20 changes: 20 additions & 0 deletions src/BenchmarkDotNet/Attributes/EventPipeProfilerAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using Microsoft.Diagnostics.NETCore.Client;

namespace BenchmarkDotNet.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class EventPipeProfilerAttribute : Attribute, IConfigSource
{
public IConfig Config { get; }

public EventPipeProfilerAttribute(EventPipeProfile profile)
{
Config = ManualConfig.CreateEmpty().AddDiagnoser(new EventPipeProfiler(profile));
}
}
}
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/BenchmarkDotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.10.0" />
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.61701" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.49" PrivateAssets="contentfiles;analyzers" />
WojciechNagorski marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
<ProjectReference Include="..\BenchmarkDotNet.Disassembler.x64\BenchmarkDotNet.Disassembler.x64.csproj">
Expand Down
73 changes: 48 additions & 25 deletions src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
Expand All @@ -11,8 +13,8 @@ namespace BenchmarkDotNet.Diagnosers
{
internal static class DiagnosersLoader
{
private const string DiagnosticAssemblyFileName = "BenchmarkDotNet.Diagnostics.Windows.dll";
private const string DiagnosticAssemblyName = "BenchmarkDotNet.Diagnostics.Windows";
private const string WindowsDiagnosticAssemblyFileName = "BenchmarkDotNet.Diagnostics.Windows.dll";
private const string WindowsDiagnosticAssemblyName = "BenchmarkDotNet.Diagnostics.Windows";

// Make the Diagnosers lazy-loaded, so they are only instantiated if needed
private static readonly Lazy<IDiagnoser[]> LazyLoadedDiagnosers
Expand All @@ -37,15 +39,12 @@ private static IDiagnoser[] LoadDiagnosers()
if (RuntimeInformation.IsFullFramework)
return LoadClassic();

// we can try to load `BenchmarkDotNet.Diagnostics.Windows` on Windows because it's using a .NET Standard compatible EventTrace lib now
if (RuntimeInformation.IsWindows())
return LoadClassic();
return LoadCoreOnWindows();

return LoadCore();
}

private static IDiagnoser[] LoadCore() => new IDiagnoser[] { MemoryDiagnoser.Default };

private static IDiagnoser[] LoadMono()
=> new IDiagnoser[]
{
Expand All @@ -54,46 +53,70 @@ private static IDiagnoser[] LoadMono()
DisassemblyDiagnoser.Create(new DisassemblyDiagnoserConfig())
};

private static IDiagnoser[] LoadCore()
=> new IDiagnoser[]
{
MemoryDiagnoser.Default,
EventPipeProfiler.Default,
};

private static IDiagnoser[] LoadCoreOnWindows()
{
List<IDiagnoser> result = new List<IDiagnoser>
{
MemoryDiagnoser.Default,
EventPipeProfiler.Default,
DisassemblyDiagnoser.Create(new DisassemblyDiagnoserConfig()),
};

// For .Net Core we can try to load `BenchmarkDotNet.Diagnostics.Windows` on Windows because it's using a .NET Standard compatible EventTrace lib now
LoadWindowsDiagnosers(result);

return result.ToArray();
}

private static IDiagnoser[] LoadClassic()
{
List<IDiagnoser> result = new List<IDiagnoser>
{
MemoryDiagnoser.Default,
DisassemblyDiagnoser.Create(new DisassemblyDiagnoserConfig())
};

LoadWindowsDiagnosers(result);

return result.ToArray();
}

private static void LoadWindowsDiagnosers(List<IDiagnoser> result)
{
try
{
var benchmarkDotNetAssembly = typeof(DefaultConfig).GetTypeInfo().Assembly;

var diagnosticsAssembly = Assembly.Load(new AssemblyName(DiagnosticAssemblyName));
var diagnosticsAssembly = Assembly.Load(new AssemblyName(WindowsDiagnosticAssemblyName));

if (diagnosticsAssembly.GetName().Version != benchmarkDotNetAssembly.GetName().Version)
{
string errorMsg =
$"Unable to load: {DiagnosticAssemblyFileName} version {diagnosticsAssembly.GetName().Version}" +
$"Unable to load: {WindowsDiagnosticAssemblyFileName} version {diagnosticsAssembly.GetName().Version}" +
Environment.NewLine +
$"Does not match: {Path.GetFileName(benchmarkDotNetAssembly.Location)} version {benchmarkDotNetAssembly.GetName().Version}";
ConsoleLogger.Default.WriteLineError(errorMsg);
}
else
{
return new[]
{
MemoryDiagnoser.Default,
DisassemblyDiagnoser.Create(new DisassemblyDiagnoserConfig()),
CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser"),
CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.PmcDiagnoser"),
CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.EtwProfiler"),
CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.ConcurrencyVisualizerProfiler"),
CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.NativeMemoryProfiler")
};
result.Add(CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser"));
result.Add(CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.PmcDiagnoser"));
result.Add(CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.EtwProfiler"));
result.Add(CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.ConcurrencyVisualizerProfiler"));
result.Add(CreateDiagnoser(diagnosticsAssembly, "BenchmarkDotNet.Diagnostics.Windows.NativeMemoryProfiler"));
}
}
catch (Exception ex) // we're loading a plug-in, better to be safe rather than sorry
{
ConsoleLogger.Default.WriteLineError($"Error loading {DiagnosticAssemblyFileName}: {ex.GetType().Name} - {ex.Message}");
ConsoleLogger.Default.WriteLineError($"Error loading {WindowsDiagnosticAssemblyFileName}: {ex.GetType().Name} - {ex.Message}");
}

return new IDiagnoser[]
{
MemoryDiagnoser.Default,
DisassemblyDiagnoser.Create(new DisassemblyDiagnoserConfig())
};
}

private static IDiagnoser CreateDiagnoser(Assembly loadedAssembly, string typeName)
Expand Down
9 changes: 9 additions & 0 deletions src/BenchmarkDotNet/Diagnosers/EventPipeProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace BenchmarkDotNet.Diagnosers
{
public enum EventPipeProfile
{
CpuSampling,
GcVerbose,
GcCollect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be great to add Jit to this list with inlining and tail call info enabled

}
}
44 changes: 44 additions & 0 deletions src/BenchmarkDotNet/Diagnosers/EventPipeProfileMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing.Parsers;

namespace BenchmarkDotNet.Diagnosers
{
internal sealed class EventPipeProfileMapper
{
internal static IReadOnlyDictionary<EventPipeProfile, EventPipeProvider[]> DotNetRuntimeProfiles { get; } = new Dictionary<EventPipeProfile, EventPipeProvider[]>
{
//Useful for tracking CPU usage and general .NET runtime information. This is the default option if no profile or providers are specified.
WojciechNagorski marked this conversation as resolved.
Show resolved Hide resolved
{ EventPipeProfile.CpuSampling,
new[]
{
new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational),
new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational, (long) ClrTraceEventParser.Keywords.Default)
}},
//Tracks GC collections and samples object allocations.
{EventPipeProfile.GcVerbose,
WojciechNagorski marked this conversation as resolved.
Show resolved Hide resolved
new[]
{
new EventPipeProvider(
name: "Microsoft-Windows-DotNETRuntime",
eventLevel: EventLevel.Verbose,
keywords: (long) ClrTraceEventParser.Keywords.GC |
(long) ClrTraceEventParser.Keywords.GCHandle |
(long) ClrTraceEventParser.Keywords.Exception
),
}},
//Tracks GC collections only at very low overhead.
WojciechNagorski marked this conversation as resolved.
Show resolved Hide resolved
{EventPipeProfile.GcCollect,
new[]
{
new EventPipeProvider(
name: "Microsoft-Windows-DotNETRuntime",
eventLevel: EventLevel.Informational,
keywords: (long) ClrTraceEventParser.Keywords.GC |
(long) ClrTraceEventParser.Keywords.Exception
)
}},
};
}
}
Loading