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