-
Notifications
You must be signed in to change notification settings - Fork 715
/
Copy path21_ObserveJitEvents.cs
274 lines (240 loc) · 12.8 KB
/
21_ObserveJitEvents.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Session;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reactive.Linq;
namespace TraceEventSamples
{
internal class ObserveJitEvents
{
/// <summary>
/// Where all the output goes.
/// </summary>
private static TextWriter Out = AllSamples.Out;
/// <summary>
/// Sample function demonstrating how to match pairs of events in a live ETW stream,
/// and compute a duration based on the start and end events. It uses CLR's JIT events
/// MethodJittingStarted, ModuleLoadUnload, and ModuleLoadUnloadVerbose.
/// for this but the principle applies to most events that mark a duration.
/// </summary>
public static void Run()
{
Out.WriteLine("******************** ObserveJitEvents DEMO ********************");
Out.WriteLine("This program Demos using the reactive framework (IObservable) to monitor");
Out.WriteLine(".NET Runtime JIT compiler events.");
Out.WriteLine();
Out.WriteLine("This program shows how you can use the reactive framework to find pairs");
Out.WriteLine("of related events (in this the JIT start and stop events) and use them");
Out.WriteLine("to calculate values (in this case the time spent JIT compiling. ");
Out.WriteLine();
Out.WriteLine("The program also shows how to create on the fly aggregate statistics using");
Out.WriteLine("the reactive framework. ");
Out.WriteLine();
Out.WriteLine("The program will print a line every time a .NET method is JIT compiled");
Out.WriteLine("in any process on the machine and will print stats every 8 methods.");
Out.WriteLine();
Out.WriteLine("Start a .NET Program while the monitoring is active to see the JIT events.");
Out.WriteLine();
if (TraceEventSession.IsElevated() != true)
{
Out.WriteLine("Must be elevated (Admin) to run this method.");
Debugger.Break();
return;
}
var monitoringTimeSec = 10;
Out.WriteLine("The monitor will run for a maximum of {0} seconds", monitoringTimeSec);
Out.WriteLine("Press Ctrl-C to stop monitoring early.");
// create a real time user mode session
using (var userSession = new TraceEventSession("ObserveJitEvents1"))
{
// Set up Ctrl-C to stop both user mode and kernel mode sessions
SetupCtrlCHandler(() => { if (userSession != null) { userSession.Stop(); } });
// enable the CLR JIT compiler events.
userSession.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)(ClrTraceEventParser.Keywords.Default));
// Get the stream of starts.
IObservable<MethodJittingStartedTraceData> jitStartStream = userSession.Source.Clr.Observe<MethodJittingStartedTraceData>("Method/JittingStarted");
// And the stream of ends.
IObservable<MethodLoadUnloadVerboseTraceData> jitEndStream = userSession.Source.Clr.Observe<MethodLoadUnloadVerboseTraceData>("Method/LoadVerbose");
// Compute the stream of matched-up pairs, and for each create a tuple of the start event and the time between the pair of events.
// Note that the 'Take(1)' is pretty important because a nested 'from' statement logically creates the 'cross product' of a two streams
// In this case the stream of starts and the stream of ends). Because we filter this stream only to matching entities and then only
// take the first entry, we stop waiting. Thus we only 'remember' those 'starts' that are not yet matched, which is very important
// for efficiency. Note that any 'lost' end events will never be matched and will accumulate over time, slowing things down.
// We should put a time window on it as well to 'forget' old start events.
var jitTimes =
from start in jitStartStream
from end in jitEndStream.Where(e => start.MethodID == e.MethodID && start.ProcessID == e.ProcessID).Take(1)
select new
{
Name = GetName(start),
ProcessID = start.ProcessID,
JitTIme = end.TimeStampRelativeMSec - start.TimeStampRelativeMSec
};
// Create a stream of just the JIT times and compute statistics every 8 methods that are JIT compiled.
IObservable<MyStatistics> jitStats = ComputeRunningStats(jitTimes, jitData => jitData.JitTIme, windowSize: 8);
// Print every time you compile a method
jitTimes.Subscribe(onNext: jitData => Out.WriteLine("JIT_TIME: {0,7:f2} PROC: {1,10} METHOD: {2}", jitData.JitTIme, GetProcessName(jitData.ProcessID), jitData.Name));
// Also output the statistics.
jitStats.Subscribe(onNext: Out.WriteLine); // print some aggregation stats
// for debugging purposes to see any events that entered by were not handled by any parser. These can be bugs.
// IObservable<TraceEvent> unhandledEventStream = userSession.Source.ObserveUnhandled();
// unhandledEventStream.Subscribe(onNext: ev => Out.WriteLine("UNHANDLED : PID: {0,5} {1}/{2} ", ev.ProcessID, ev.ProviderName, ev.EventName));
// Set up a timer to stop processing after monitoringTimeSec
IObservable<long> timer = Observable.Timer(new TimeSpan(0, 0, monitoringTimeSec));
timer.Subscribe(delegate
{
Out.WriteLine("Stopped after {0} sec", monitoringTimeSec);
userSession.Stop();
});
// OK we are all set up, time to listen for events and pass them to the observers.
userSession.Source.Process();
}
}
/// <summary>
/// The JIT start event breaks a name into its pieces. Reform the name from the pieces.
/// </summary>
private static string GetName(MethodJittingStartedTraceData data)
{
// Prepare sig (strip return value)
var sig = "";
var sigWithRet = data.MethodSignature;
var parenIdx = sigWithRet.IndexOf('(');
if (0 <= parenIdx)
{
sig = sigWithRet.Substring(parenIdx);
}
// prepare class name (strip namespace)
var className = data.MethodNamespace;
var lastDot = className.LastIndexOf('.');
if (0 <= lastDot)
{
className = className.Substring(lastDot + 1);
}
var sep = ".";
if (className.Length == 0)
{
sep = "";
}
return className + sep + data.MethodName + sig;
}
/// <summary>
/// Generate an IObservable of statistics from a field in a source IObservable, for "windows" a sizes specified by
/// "windowSize"
/// </summary>
/// <typeparam name="T">Observed type</typeparam>
/// <param name="source">The initial sequence</param>
/// <param name="selector">A selector from the observed type to a double field</param>
/// <param name="windowSize">Size of the window used for computing the statistical values</param>
/// <returns>IObservable{Statistics}</returns>
private static IObservable<MyStatistics> ComputeRunningStats<T>(IObservable<T> source, Func<T, double> selector, int windowSize)
{
// Create a stream of floating point valeus from the stream of Ts
IObservable<double> values = from item in source select selector(item);
// for each new data point, compute the new running sum to for groups of the last 'windowSize' data points.
var accums = from window in values.Window(windowSize)
from accum in window.Aggregate(
new { curCount = 0, curSum = 0.0, curSumSquares = 0.0, curMin = double.PositiveInfinity, curMax = double.NegativeInfinity },
(acc, value) => new
{
curCount = acc.curCount + 1,
curSum = acc.curSum + value,
curSumSquares = acc.curSumSquares + value * value,
curMin = (acc.curMin > value) ? value : acc.curMin,
curMax = (acc.curMax < value) ? value : acc.curMax,
})
select accum;
// For each accumulation in the stream, compute map it to the statistics for that accumulation.
var stats = from accum in accums
select new MyStatistics
{
Count = accum.curCount,
Average = accum.curSum / accum.curCount,
Deviation = Math.Sqrt((accum.curCount * accum.curSumSquares - accum.curSum * accum.curSum) / (accum.curCount * accum.curCount - 1)),
Min = accum.curMin,
Max = accum.curMax,
};
return stats;
}
/// <summary>
/// Returns the process name for a given process ID
/// </summary>
private static string GetProcessName(int processID)
{
// Only keep the cache for 10 seconds to avoid issues with process ID reuse.
var now = DateTime.UtcNow;
if ((now - s_processNameCacheLastUpdate).TotalSeconds > 10)
{
s_processNameCache.Clear();
}
s_processNameCacheLastUpdate = now;
string ret = null;
if (!s_processNameCache.TryGetValue(processID, out ret))
{
Process proc = null;
try { proc = Process.GetProcessById(processID); }
catch (Exception) { }
if (proc != null)
{
ret = proc.ProcessName;
}
if (string.IsNullOrWhiteSpace(ret))
{
ret = processID.ToString();
}
s_processNameCache.Add(processID, ret);
}
return ret;
}
private static Dictionary<int, string> s_processNameCache = new Dictionary<int, string>();
private static DateTime s_processNameCacheLastUpdate;
#region Console CtrlC handling
private static bool s_bCtrlCExecuted;
private static ConsoleCancelEventHandler s_CtrlCHandler;
/// <summary>
/// This implementation allows one to call this function multiple times during the
/// execution of a console application. The CtrlC handling is disabled when Ctrl-C
/// is typed, one will need to call this method again to re-enable it.
/// </summary>
/// <param name="action"></param>
private static void SetupCtrlCHandler(Action action)
{
s_bCtrlCExecuted = false;
// uninstall previous handler
if (s_CtrlCHandler != null)
{
Console.CancelKeyPress -= s_CtrlCHandler;
}
s_CtrlCHandler =
(object sender, ConsoleCancelEventArgs cancelArgs) =>
{
if (!s_bCtrlCExecuted)
{
s_bCtrlCExecuted = true; // ensure non-re-entrancy
Out.WriteLine("Stopping monitor");
action(); // execute custom action
// terminate normally (i.e. when the monitoring tasks complete b/c we've stopped the sessions)
cancelArgs.Cancel = true;
}
};
Console.CancelKeyPress += s_CtrlCHandler;
}
#endregion
}
/// <summary>
/// Class containing a set of statistical values
/// </summary>
public class MyStatistics
{
public int Count;
public double Average;
public double Deviation;
public double Min;
public double Max;
public override string ToString()
{ return string.Format("STATS: count {0} avg {1:F1}. stddev {2:F1}. min {3:F1}. max {4:F1}.", Count, Average, Deviation, Min, Max); }
}
}