public GCHeapSimulators(TraceLog traceLog, TraceEventDispatcher source, MutableTraceEventStackSource stackSource, TextWriter log) { m_simulators = new GCHeapSimulator[traceLog.Processes.Count]; m_source = source; m_stackSource = stackSource; m_log = log; // Save a symbol resolver for this trace log. s_typeNameSymbolResolvers[traceLog.FilePath] = new TypeNameSymbolResolver(traceLog.FilePath, log); var etwClrProfileTraceEventParser = new ETWClrProfilerTraceEventParser(source); etwClrProfileTraceEventParser.ClassIDDefintion += CheckForNewProcess; source.Clr.GCSampledObjectAllocation += CheckForNewProcess; source.Clr.GCAllocationTick += CheckForNewProcessForTick; }
/// <summary> /// Create a GC simulation using the events from 'source' for the process with process ID 'processID'. /// The stacks associated with allocation are added to 'stackSource' (and a pseudo-frame with the type /// is added to the stacks for each allocation. If useOnlyAllocationTicks, it will not use /// either the etwClrProfiler or GCSampledObjectAllocation as allocation events (so you can reliably /// get a coarse sampled simulation independent of what other events are in the trace). /// </summary> public GCHeapSimulator(TraceEventDispatcher source, TraceProcess process, MutableTraceEventStackSource stackSource, TextWriter log, bool useOnlyAllocationTicks = false) { m_processID = process.ProcessID; m_process = process; m_pointerSize = 4; // We guess this, It is OK for this to be wrong. m_typeNameSymbolResolver = GCHeapSimulators.TypeNameSymbolResolvers[process.Log.FilePath]; if (m_typeNameSymbolResolver == null) { m_typeNameSymbolResolver = GCHeapSimulators.TypeNameSymbolResolvers[process.Log.FilePath] = new TypeNameSymbolResolver(process.Log.FilePath, log); } m_ObjsInGen = new Dictionary <Address, GCHeapSimulatorObject> [4]; // generation 0-2 + 1 for gen 2 for (int i = 0; i < m_ObjsInGen.Length; i++) { m_ObjsInGen[i] = new Dictionary <Address, GCHeapSimulatorObject>(10000); // Stays in small object heap } m_classNamesAsFrames = new Dictionary <ulong, TypeInfo>(500); m_stackSource = stackSource; AllocateObject = () => new GCHeapSimulatorObject(); // Register all the callbacks. if (!useOnlyAllocationTicks) { var etwClrProfilerTraceEventParser = new ETWClrProfilerTraceEventParser(source); etwClrProfilerTraceEventParser.GCStart += delegate(Microsoft.Diagnostics.Tracing.Parsers.ETWClrProfiler.GCStartArgs data) { m_useEtlClrProfilerEvents = true; OnGCStart(data, data.GCID, data.Generation); }; etwClrProfilerTraceEventParser.ObjectAllocated += delegate(ObjectAllocatedArgs data) { m_useEtlClrProfilerEvents = true; OnObjectAllocated(data, data.ObjectID, data.ClassID, data.Size, data.RepresentativeSize); }; etwClrProfilerTraceEventParser.ObjectsMoved += OnObjectMoved; etwClrProfilerTraceEventParser.ObjectsSurvived += OnObjectSurvived; etwClrProfilerTraceEventParser.GCStop += delegate(GCStopArgs data) { OnGCStop(data, data.GCID); }; etwClrProfilerTraceEventParser.ClassIDDefintion += OnClassIDDefintion; // TODO do we need module info? // etwClrProfileTraceEventParser.ModuleIDDefintion += OnModuleIDDefintion; } source.Clr.GCStart += delegate(Microsoft.Diagnostics.Tracing.Parsers.Clr.GCStartTraceData data) { if (m_useEtlClrProfilerEvents) { return; } OnGCStart(data, data.Count, data.Depth); }; source.Clr.GCStop += delegate(GCEndTraceData data) { if (m_useEtlClrProfilerEvents) { return; } OnGCStop(data, data.Count); }; source.Clr.GCBulkMovedObjectRanges += OnEtwObjectMoved; source.Clr.GCBulkSurvivingObjectRanges += OnEtwObjectSurvived; source.Clr.TypeBulkType += OnEtwClassIDDefintion; if (!useOnlyAllocationTicks) { source.Clr.GCSampledObjectAllocation += delegate(GCSampledObjectAllocationTraceData data) { // Compute size per object, Projecting against divide by zero. long representativeSize = data.TotalSizeForTypeSample; var objectCount = data.ObjectCountForTypeSample; if (objectCount > 1) { representativeSize = representativeSize / objectCount; } OnObjectAllocated(data, data.Address, data.TypeID, data.TotalSizeForTypeSample, representativeSize); }; } else { source.Clr.GCAllocationTick += OnEtwGCAllocationTick; } }
static void Main(string[] args) { // This is the name of the event source. // Given just the name of the eventSource you can get the GUID for the evenSource by calling this API. // From a ETW perspective, the GUID is the 'true name' of the EventSource. var providerGuid2 = Guid.Parse("{E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4}"); // Today you have to be Admin to turn on ETW events (anyone can write ETW events). if (!(TraceEventSession.IsElevated() ?? false)) { Console.WriteLine( "To turn on ETW events you need to be Administrator, please run from an Admin process."); } // As mentioned below, sessions can outlive the process that created them. Thus you need a way of // naming the session so that you can 'reconnect' to it from another process. This is what the name // is for. It can be anything, but it should be descriptive and unique. If you expect mulitple versions // of your program to run simultaneously, you need to generate unique names (e.g. add a process ID suffix) Console.WriteLine("Creating a 'My Session' session"); var sessionName = "My Session"; using (var session = new TraceEventSession(sessionName, null)) // the null second parameter means 'real time session' { // Note that sessions create a OS object (a session) that lives beyond the lifetime of the process // that created it (like Filles), thus you have to be more careful about always cleaning them up. // An importanty way you can do this is to set the 'StopOnDispose' property which will cause the session to // stop (and thus the OS object will die) when the TraceEventSession dies. Because we used a 'using' // statement, this means that any exception in the code below will clean up the OS object. session.StopOnDispose = true; // By default, if you hit Ctrl-C your .NET objects may not be disposed, so force it to. It is OK if dispose is called twice. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { session.Dispose(); }; // prepare to read from the session, connect the ETWTraceEventSource to the session using (var source = new ETWTraceEventSource(sessionName, TraceEventSourceType.Session)) { // To demonstrate non-trivial event manipuation, we calculate the time delta between 'MyFirstEvent and 'MySecondEvent' // These variables are used in this calculation int lastMyEventID = int.MinValue; // an illegal value to start with. double lastMyEventMSec = 0; // Hook up the parser that knows about EventSources var callParser = new ETWClrProfilerTraceEventParser(source); callParser.AddCallbackForEvents(delegate(TraceEvent eventdata) { using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\etwdata.txt", true)) { file.WriteLine(eventdata.EventName + eventdata); } }); //parser2.All += delegate (TraceEvent data) //{ // using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\etwdata.txt", true)) // { // file.WriteLine(data.EventName); // } // Console.WriteLine("Event : " + data.EventName); //}; // Enable my provider, you can call many of these on the same session to get other events. session.EnableProvider(ETWClrProfilerTraceEventParser.ProviderGuid, TraceEventLevel.Always, (ulong)ETWClrProfilerTraceEventParser.Keywords.Call); Console.WriteLine("Staring Listing for events"); // go into a loop processing events can calling the callbacks. Because this is live data (not from a file) // processing never completes by itself, but only because someone called 'source.Close()'. source.Process(); Console.WriteLine(); Console.WriteLine("Stopping the collection of events."); } } }