Skip to content

Commit 755e96c

Browse files
committed
Perftests: experimental proofcode for ETW metrics
1 parent 97be847 commit 755e96c

File tree

10 files changed

+527
-8
lines changed

10 files changed

+527
-8
lines changed

Experimental/tests-performance/CodeJam.Experimental-Tests.Performance.csproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
1313
<FileAlignment>512</FileAlignment>
1414
<TargetFrameworkProfile />
15+
<NuGetPackageImportStamp>
16+
</NuGetPackageImportStamp>
1517
</PropertyGroup>
1618
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1719
<DebugSymbols>true</DebugSymbols>
@@ -40,6 +42,9 @@
4042
<Reference Include="BenchmarkDotNet.Core, Version=0.10.8.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
4143
<HintPath>..\..\packages\BenchmarkDotNet.Core.0.10.8\lib\net46\BenchmarkDotNet.Core.dll</HintPath>
4244
</Reference>
45+
<Reference Include="Microsoft.Diagnostics.Tracing.TraceEvent, Version=1.0.41.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
46+
<HintPath>..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll</HintPath>
47+
</Reference>
4348
<Reference Include="Microsoft.DotNet.InternalAbstractions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
4449
<HintPath>..\..\packages\Microsoft.DotNet.InternalAbstractions.1.0.0\lib\net451\Microsoft.DotNet.InternalAbstractions.dll</HintPath>
4550
</Reference>
@@ -64,10 +69,14 @@
6469
<Reference Include="System.Xml" />
6570
</ItemGroup>
6671
<ItemGroup>
72+
<Compile Include="ETWMetricsPrototype\EtwDiagnozer.cs" />
6773
<Compile Include="IntervalTree\IntervalTreeCostin.cs" />
6874
<Compile Include="IntervalTree\IntervalTree.cs" />
6975
<Compile Include="IntervalTree\IntervalTreeTests.cs" />
7076
<Compile Include="IntervalTree\IntervalTreePerfTest.cs" />
77+
<Compile Include="ETWMetricsPrototype\IoDiagnoser.cs" />
78+
<Compile Include="ETWMetricsPrototype\IoPerfTest.cs" />
79+
<Compile Include="ETWMetricsPrototype\LogCapture.cs" />
7180
<Compile Include="Properties\AssemblyInfo.cs" />
7281
</ItemGroup>
7382
<ItemGroup>
@@ -100,6 +109,13 @@
100109
</ProjectReference>
101110
</ItemGroup>
102111
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
112+
<Import Project="..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets" Condition="Exists('..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" />
113+
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
114+
<PropertyGroup>
115+
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
116+
</PropertyGroup>
117+
<Error Condition="!Exists('..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets'))" />
118+
</Target>
103119
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
104120
Other similar extension points exist, see Microsoft.Common.targets.
105121
<Target Name="BeforeBuild">
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using BenchmarkDotNet.Parameters;
4+
using BenchmarkDotNet.Running;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.IO;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using BenchmarkDotNet.Diagnosers;
11+
12+
using CodeJam;
13+
using CodeJam.Reflection;
14+
15+
using Microsoft.Diagnostics.Tracing;
16+
using Microsoft.Diagnostics.Tracing.Parsers;
17+
using Microsoft.Diagnostics.Tracing.Session;
18+
19+
namespace BenchmarkDotNet.Diagnostics.Windows
20+
{
21+
public abstract class EtwDiagnoser<TStats> where TStats : new()
22+
{
23+
// TODO: to a stateless object with all state stored in the run slot
24+
protected readonly LogCapture Logger = new LogCapture();
25+
protected readonly Dictionary<Benchmark, int> BenchmarkToProcess = new Dictionary<Benchmark, int>();
26+
protected readonly ConcurrentDictionary<int, TStats> StatsPerProcess = new ConcurrentDictionary<int, TStats>();
27+
28+
protected TraceEventSession Session { get; private set; }
29+
30+
protected abstract ulong EventType { get; }
31+
32+
protected abstract string SessionNamePrefix { get; }
33+
34+
protected void Start(DiagnoserActionParameters parameters)
35+
{
36+
Clear();
37+
38+
BenchmarkToProcess.Add(parameters.Benchmark, parameters.Process.Id);
39+
StatsPerProcess.TryAdd(parameters.Process.Id, GetInitializedStats(parameters));
40+
41+
WorkaroundEnsureNativeDlls();
42+
43+
// TOD: HACK: copy native files into assembly location
44+
Session = CreateSession(parameters.Benchmark);
45+
46+
EnableProvider();
47+
48+
AttachToEvents(Session, parameters.Benchmark);
49+
50+
// The ETW collection thread starts receiving events immediately, but we only
51+
// start aggregating them after ProcessStarted is called and we know which process
52+
// (or processes) we should be monitoring. Communication between the benchmark thread
53+
// and the ETW collection thread is through the statsPerProcess concurrent dictionary
54+
// and through the TraceEventSession class, which is thread-safe.
55+
var task = Task.Factory.StartNew((Action)(() => Session.Source.Process()), TaskCreationOptions.LongRunning);
56+
57+
// wait until the processing has started, block by then so we don't loose any
58+
// information (very important for jit-related things)
59+
WaitUntilStarted(task);
60+
}
61+
62+
//HACK: Fixes https://github.com/Microsoft/perfview/issues/292
63+
private void WorkaroundEnsureNativeDlls()
64+
{
65+
var etwAssembly = typeof(ETWTraceEventSource).Assembly;
66+
var location = Path.GetDirectoryName(etwAssembly.Location);
67+
var codebase = etwAssembly.GetAssemblyDirectory();
68+
if (!location.Equals(codebase, StringComparison.InvariantCultureIgnoreCase))
69+
{
70+
DebugCode.BugIf(
71+
Path.GetFullPath(location).ToUpperInvariant() == Path.GetFullPath(codebase).ToUpperInvariant(),
72+
"Path.GetFullPath(location).ToUpperInvariant() == Path.GetFullPath(codebase).ToUpperInvariant()");
73+
74+
CopyDirectoryIfExists(
75+
Path.Combine(codebase, "amd64"),
76+
Path.Combine(location, "amd64"));
77+
78+
79+
CopyDirectoryIfExists(
80+
Path.Combine(codebase, "x86"),
81+
Path.Combine(location, "x86"));
82+
}
83+
}
84+
85+
private void CopyDirectoryIfExists(string source, string target, bool overwrite = false)
86+
{
87+
source = Path.GetFullPath(source);
88+
target = Path.GetFullPath(target);
89+
90+
if (!source.EndsWith("\\") && !source.EndsWith("/") && !source.EndsWith(":"))
91+
source += "\\";
92+
93+
if (!target.EndsWith("\\") && !target.EndsWith("/") && !target.EndsWith(":"))
94+
target += "\\";
95+
96+
if (!Directory.Exists(source))
97+
return;
98+
99+
100+
if (!Directory.Exists(target))
101+
Directory.CreateDirectory(target);
102+
103+
foreach (var sourceDirectory in Directory.GetDirectories(source, "*", SearchOption.AllDirectories))
104+
{
105+
var targetDirectory = Path.Combine(target, sourceDirectory.Substring(source.Length));
106+
Directory.CreateDirectory(targetDirectory);
107+
}
108+
109+
110+
foreach (var sourceFile in Directory.GetFiles(source, "*", SearchOption.AllDirectories))
111+
{
112+
var targetFile = Path.Combine(target, sourceFile.Substring(source.Length));
113+
if (overwrite || !File.Exists(targetFile))
114+
{
115+
File.Copy(sourceFile, targetFile, overwrite);
116+
}
117+
}
118+
}
119+
120+
protected virtual TStats GetInitializedStats(DiagnoserActionParameters parameters) => new TStats();
121+
122+
/// <summary>Creates the session.</summary>
123+
/// <param name="benchmark">The benchmark.</param>
124+
/// <returns></returns>
125+
protected virtual TraceEventSession CreateSession(Benchmark benchmark)
126+
=> new TraceEventSession(KernelTraceEventParser.KernelSessionName);
127+
128+
protected virtual void EnableProvider()
129+
{
130+
Session.EnableProvider(
131+
ClrTraceEventParser.ProviderGuid,
132+
TraceEventLevel.Verbose,
133+
EventType);
134+
}
135+
136+
protected abstract void AttachToEvents(TraceEventSession traceEventSession, Benchmark benchmark);
137+
138+
protected void Stop()
139+
{
140+
WaitForDelayedEvents();
141+
142+
Session.Dispose();
143+
}
144+
145+
private void Clear()
146+
{
147+
BenchmarkToProcess.Clear();
148+
StatsPerProcess.Clear();
149+
}
150+
151+
private static string GetSessionName(string prefix, Benchmark benchmark, ParameterInstances parameters = null)
152+
{
153+
if (parameters != null && parameters.Items.Count > 0)
154+
return $"{prefix}-{benchmark.FolderInfo}-{parameters.FolderInfo}";
155+
return $"{prefix}-{benchmark.FolderInfo}";
156+
}
157+
158+
private static void WaitUntilStarted(Task task)
159+
{
160+
while (task.Status == TaskStatus.Created
161+
|| task.Status == TaskStatus.WaitingForActivation
162+
|| task.Status == TaskStatus.WaitingToRun)
163+
{
164+
Thread.Sleep(10);
165+
}
166+
}
167+
168+
/// <summary>
169+
/// ETW real-time sessions receive events with a slight delay. Typically it
170+
/// shouldn't be more than a few seconds. This increases the likelihood that
171+
/// all relevant events are processed by the collection thread by the time we
172+
/// are done with the benchmark.
173+
/// </summary>
174+
private static void WaitForDelayedEvents()
175+
{
176+
Thread.Sleep(TimeSpan.FromSeconds(3));
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)