in src/TraceEvent/TraceLog.cs [1079:1827]
private unsafe void SetupCallbacks(TraceEventDispatcher rawEvents)
{
processingDisabled = false;
removeFromStream = false;
bookKeepingEvent = false; // BookKeeping events are removed from the stream by default
bookeepingEventThatMayHaveStack = false; // Some bookkeeping events (ThreadDCEnd) might have stacks
noStack = false; // This event should never have a stack associated with it, so skip them if we every try to attach a stack.
numberOnPage = eventsPerPage;
pastEventInfo = new PastEventInfo(this);
eventCount = 0;
// FIX NOW HACK, because Method and Module unload methods are missing.
jittedMethods = new List<MethodLoadUnloadVerboseTraceData>();
jsJittedMethods = new List<MethodLoadUnloadJSTraceData>();
sourceFilesByID = new Dictionary<JavaScriptSourceKey, string>();
// If this is a ETL file, we also need to compute all the normal TraceLog stuff the raw stream
pointerSize = rawEvents.PointerSize;
_syncTimeUTC = rawEvents._syncTimeUTC;
_syncTimeQPC = rawEvents._syncTimeQPC;
_QPCFreq = rawEvents._QPCFreq;
sessionStartTimeQPC = rawEvents.sessionStartTimeQPC;
sessionEndTimeQPC = rawEvents.sessionEndTimeQPC;
cpuSpeedMHz = rawEvents.CpuSpeedMHz;
numberOfProcessors = rawEvents.NumberOfProcessors;
eventsLost = rawEvents.EventsLost;
osVersion = rawEvents.OSVersion;
// These parsers create state and we want to collect that so we put it on our 'parsers' list that we serialize.
var kernelParser = rawEvents.Kernel;
// If a event does not have a callback, then it will be treated as unknown. Unfortunately this also means that the
// virtual method 'LogCodeAddresses() will not fire. Thus any event that has this overload needs to have a callback.
// The events below don't otherwise need a callback, but we add one so that LogCodeAddress() works.
Action<TraceEvent> doNothing = delegate (TraceEvent data) { };
// TODO: I have given up for now. IN addition to the events with LogCodeAddress, you also need any event with FixupData()
// methods associated with them. There are enough of these that I did not want to do them one by one (mostly because of fragility)
// Also kernel events have the potential for being before the process start event, and we need to see these to fix this. (mostly memory / virtual alloc events).
kernelParser.All += doNothing;
// We want high volume events to be looked up properly since GetEventCount() is slower thant we want.
rawEvents.Clr.GCAllocationTick += doNothing;
rawEvents.Clr.GCJoin += doNothing;
rawEvents.Clr.GCFinalizeObject += doNothing;
rawEvents.Clr.MethodJittingStarted += doNothing;
//kernelParser.AddCallbackForEvents<PageFaultTraceData>(doNothing); // Lots of page fault ones
//kernelParser.AddCallbackForEvents<PageAccessTraceData>(doNothing);
//kernelParser.PerfInfoSysClEnter += doNothing;
//kernelParser.PMCCounterProf += doNothing;
Debug.Assert(((eventsPerPage - 1) & eventsPerPage) == 0, "eventsPerPage must be a power of 2");
kernelParser.EventTraceHeader += delegate (EventTraceHeaderTraceData data)
{
bootTime100ns = data.BootTime100ns;
if (_syncTimeQPC == 0)
{ // This is for the TraceLog, not just for the ETWTraceEventSource
_syncTimeQPC = data.TimeStampQPC;
sessionStartTimeQPC += data.TimeStampQPC;
sessionEndTimeQPC += data.TimeStampQPC;
}
if (!utcOffsetMinutes.HasValue)
{
utcOffsetMinutes = -data.UTCOffsetMinutes;
if (SessionStartTime.IsDaylightSavingTime())
{
utcOffsetMinutes += 60; // Compensate for Daylight savings time.
}
}
};
kernelParser.SystemConfigCPU += delegate (SystemConfigCPUTraceData data)
{
memorySizeMeg = data.MemSize;
if (data.DomainName.Length > 0)
{
machineName = data.ComputerName + "." + data.DomainName;
}
else
{
machineName = data.ComputerName;
}
};
kernelParser.SysConfigBuildInfo += delegate (BuildInfoTraceData data)
{
osName = data.ProductName;
osBuild = data.BuildLab;
};
// Process level events.
kernelParser.ProcessStartGroup += delegate (ProcessTraceData data)
{
processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC, data.Opcode == TraceEventOpcode.Start).ProcessStart(data);
// Don't filter them out (not that many, useful for finding command line)
};
kernelParser.ProcessEndGroup += delegate (ProcessTraceData data)
{
processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).ProcessEnd(data);
// Don't filter them out (not that many, useful for finding command line)
};
// Thread level events
kernelParser.ThreadStartGroup += delegate (ThreadTraceData data)
{
TraceProcess process = processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, data.TimeStampQPC, process, data.Opcode == TraceEventOpcode.Start || data.Opcode == TraceEventOpcode.DataCollectionStart);
thread.startTimeQPC = data.TimeStampQPC;
thread.userStackBase = data.UserStackBase;
if (data.Opcode == TraceEventOpcode.DataCollectionStart)
{
bookKeepingEvent = true;
thread.startTimeQPC = sessionStartTimeQPC;
}
else if (data.Opcode == TraceEventOpcode.Start)
{
var threadProc = thread.Process;
if (!threadProc.anyThreads)
{
// We saw a real process start (not a DCStart or a non at all)
if (sessionStartTimeQPC < threadProc.startTimeQPC && threadProc.startTimeQPC < data.TimeStampQPC)
{
thread.threadInfo = "Startup Thread";
}
threadProc.anyThreads = true;
}
}
};
kernelParser.ThreadSetName += delegate (ThreadSetNameTraceData data)
{
CategorizeThread(data, data.ThreadName);
};
kernelParser.ThreadEndGroup += delegate (ThreadTraceData data)
{
TraceProcess process = processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, data.TimeStampQPC, process);
if (thread.process == null)
{
thread.process = process;
}
if (data.ThreadName.Length > 0)
{
CategorizeThread(data, data.ThreadName);
}
Debug.Assert(thread.process == process, "Different events disagree on the process object!");
DebugWarn(thread.endTimeQPC == long.MaxValue || thread.ThreadID == 0,
"Thread end on a terminated thread " + data.ThreadID + " that ended at " + QPCTimeToRelMSec(thread.endTimeQPC), data);
DebugWarn(thread.Process.endTimeQPC == long.MaxValue, "Thread ending on ended process", data);
thread.endTimeQPC = data.TimeStampQPC;
thread.userStackBase = data.UserStackBase;
if (data.Opcode == TraceEventOpcode.DataCollectionStop)
{
thread.endTimeQPC = sessionEndTimeQPC;
bookKeepingEvent = true;
bookeepingEventThatMayHaveStack = true;
}
// Keep threadIDtoThread table under control by removing old entries.
if (IsRealTime)
{
Threads.threadIDtoThread.Remove(data.ThreadID);
}
};
// ModuleFile level events
DbgIDRSDSTraceData lastDbgData = null;
ImageIDTraceData lastImageIDData = null;
FileVersionTraceData lastFileVersionData = null;
TraceModuleFile lastTraceModuleFile = null;
long lastTraceModuleFileQPC = 0;
kernelParser.ImageGroup += delegate (ImageLoadTraceData data)
{
var isLoad = ((data.Opcode == (TraceEventOpcode)10) || (data.Opcode == TraceEventOpcode.DataCollectionStart));
// TODO is this a good idea? It tries to undo the anonimization a bit.
var fileName = data.FileName;
if (fileName.EndsWith("########")) // We threw away the DLL name
{
// But at least we have the DLL file name (not the path).
if (lastImageIDData != null && data.TimeStampQPC == lastImageIDData.TimeStampQPC)
{
var anonomizedIdx = fileName.IndexOf("########");
fileName = fileName.Substring(0, anonomizedIdx + 8) + @"\" + lastImageIDData.OriginalFileName;
}
}
var moduleFile = processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ImageLoadOrUnload(data, isLoad, fileName);
// TODO review: is using the timestamp the best way to make the association
if (lastDbgData != null && data.TimeStampQPC == lastDbgData.TimeStampQPC)
{
moduleFile.pdbName = lastDbgData.PdbFileName;
moduleFile.pdbSignature = lastDbgData.GuidSig;
moduleFile.pdbAge = lastDbgData.Age;
// There is no guarantee that the names of the DLL and PDB match, but they do 99% of the time
// We tolerate the exceptions, because it is a useful check most of the time
Debug.Assert(RoughDllPdbMatch(moduleFile.fileName, moduleFile.pdbName));
}
moduleFile.timeDateStamp = data.TimeDateStamp;
moduleFile.imageChecksum = data.ImageChecksum;
if (moduleFile.timeDateStamp == 0 && lastImageIDData != null && data.TimeStampQPC == lastImageIDData.TimeStampQPC)
{
moduleFile.timeDateStamp = lastImageIDData.TimeDateStamp;
}
if (lastFileVersionData != null && data.TimeStampQPC == lastFileVersionData.TimeStampQPC)
{
moduleFile.fileVersion = lastFileVersionData.FileVersion;
moduleFile.productVersion = lastFileVersionData.ProductVersion;
moduleFile.productName = lastFileVersionData.ProductName;
}
// Remember this ModuleFile because there can be Image* events after this with
// the same timestamp that have information that we need to put into it
// (the logic above handles the case when those other events are first).
lastTraceModuleFile = moduleFile;
lastTraceModuleFileQPC = data.TimeStampQPC;
};
var symbolParser = new SymbolTraceEventParser(rawEvents);
// Symbol parser events never have a stack (but will have a QPC associated with the imageLoad) so we want them ignored
symbolParser.All += delegate (TraceEvent data) { noStack = true; };
symbolParser.ImageIDDbgID_RSDS += delegate (DbgIDRSDSTraceData data)
{
hasPdbInfo = true;
// The ImageIDDbgID_RSDS may be after the ImageLoad
if (lastTraceModuleFile != null && lastTraceModuleFileQPC == data.TimeStampQPC && string.IsNullOrEmpty(lastTraceModuleFile.pdbName))
{
lastTraceModuleFile.pdbName = data.PdbFileName;
lastTraceModuleFile.pdbSignature = data.GuidSig;
lastTraceModuleFile.pdbAge = data.Age;
// There is no guarantee that the names of the DLL and PDB match, but they do 99% of the time
// We tolerate the exceptions, because it is a useful check most of the time
Debug.Assert(RoughDllPdbMatch(lastTraceModuleFile.fileName, lastTraceModuleFile.pdbName));
lastDbgData = null;
}
else // Or before (it is handled in ImageGroup callback above)
{
lastDbgData = (DbgIDRSDSTraceData)data.Clone();
}
};
symbolParser.ImageID += delegate (ImageIDTraceData data)
{
// The ImageID may be after the ImageLoad
if (lastTraceModuleFile != null && lastTraceModuleFileQPC == data.TimeStampQPC && lastTraceModuleFile.timeDateStamp == 0)
{
lastTraceModuleFile.timeDateStamp = data.TimeDateStamp;
lastImageIDData = null;
}
else // Or before (it is handled in ImageGroup callback above)
{
lastImageIDData = (ImageIDTraceData)data.Clone();
}
};
symbolParser.ImageIDFileVersion += delegate (FileVersionTraceData data)
{
// The ImageIDFileVersion may be after the ImageLoad
if (lastTraceModuleFile != null && lastTraceModuleFileQPC == data.TimeStampQPC && lastTraceModuleFile.fileVersion == null)
{
lastTraceModuleFile.fileVersion = data.FileVersion;
lastTraceModuleFile.productVersion = data.ProductVersion;
lastTraceModuleFile.productName = data.ProductName;
lastFileVersionData = null;
}
else // Or before (it is handled in ImageGroup callback above)
{
lastFileVersionData = (FileVersionTraceData)data.Clone();
}
};
kernelParser.AddCallbackForEvents<FileIONameTraceData>(delegate (FileIONameTraceData data)
{
bookKeepingEvent = true;
});
rawEvents.Clr.LoaderModuleLoad += delegate (ModuleLoadUnloadTraceData data)
{
processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ManagedModuleLoadOrUnload(data, true, false);
};
rawEvents.Clr.LoaderModuleUnload += delegate (ModuleLoadUnloadTraceData data)
{
processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ManagedModuleLoadOrUnload(data, false, false);
};
rawEvents.Clr.LoaderModuleDCStopV2 += delegate (ModuleLoadUnloadTraceData data)
{
processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ManagedModuleLoadOrUnload(data, false, true);
};
var ClrRundownParser = new ClrRundownTraceEventParser(rawEvents);
Action<ModuleLoadUnloadTraceData> onLoaderRundown = delegate (ModuleLoadUnloadTraceData data)
{
processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ManagedModuleLoadOrUnload(data, false, true);
};
ClrRundownParser.LoaderModuleDCStop += onLoaderRundown;
ClrRundownParser.LoaderModuleDCStart += onLoaderRundown;
Action<MethodLoadUnloadVerboseTraceData> onMethodStart = delegate (MethodLoadUnloadVerboseTraceData data)
{
// We only capture data on unload, because we collect the addresses first.
if (!data.IsDynamic && !data.IsJitted)
{
bookKeepingEvent = true;
}
if ((int)data.ID == 139) // MethodDCStartVerboseV2
{
bookKeepingEvent = true;
}
if (data.IsJitted)
{
TraceProcess process = processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
process.InsertJITTEDMethod(data.MethodStartAddress, data.MethodSize, delegate ()
{
TraceManagedModule module = process.LoadedModules.GetOrCreateManagedModule(data.ModuleID, data.TimeStampQPC);
MethodIndex methodIndex = CodeAddresses.Methods.NewMethod(TraceLog.GetFullName(data), module.ModuleFile.ModuleFileIndex, data.MethodToken);
return new TraceProcess.MethodLookupInfo(data.MethodStartAddress, data.MethodSize, methodIndex);
});
jittedMethods.Add((MethodLoadUnloadVerboseTraceData)data.Clone());
}
};
rawEvents.Clr.MethodLoadVerbose += onMethodStart;
rawEvents.Clr.MethodDCStartVerboseV2 += onMethodStart;
ClrRundownParser.MethodDCStartVerbose += onMethodStart;
rawEvents.Clr.MethodUnloadVerbose += delegate (MethodLoadUnloadVerboseTraceData data)
{
codeAddresses.AddMethod(data);
if (!data.IsJitted)
{
bookKeepingEvent = true;
}
};
rawEvents.Clr.MethodILToNativeMap += delegate (MethodILToNativeMapTraceData data)
{
codeAddresses.AddILMapping(data);
bookKeepingEvent = true;
};
ClrRundownParser.MethodILToNativeMapDCStop += delegate (MethodILToNativeMapTraceData data)
{
codeAddresses.AddILMapping(data);
bookKeepingEvent = true;
};
Action<MethodLoadUnloadVerboseTraceData> onMethodDCStop = delegate (MethodLoadUnloadVerboseTraceData data)
{
#if false // TODO this is a hack for VS traces that only did DCStarts but no DCStops.
if (data.IsJitted && data.TimeStampRelativeMSec < 4000)
{
jittedMethods.Add((MethodLoadUnloadVerboseTraceData)data.Clone());
}
#endif
codeAddresses.AddMethod(data);
bookKeepingEvent = true;
};
rawEvents.Clr.MethodDCStopVerboseV2 += onMethodDCStop;
ClrRundownParser.MethodDCStopVerbose += onMethodDCStop;
var jScriptParser = new JScriptTraceEventParser(rawEvents);
jScriptParser.AddCallbackForEvents<Microsoft.Diagnostics.Tracing.Parsers.JScript.SourceLoadUnloadTraceData>(
delegate (Microsoft.Diagnostics.Tracing.Parsers.JScript.SourceLoadUnloadTraceData data)
{
sourceFilesByID[new JavaScriptSourceKey(data.SourceID, data.ScriptContextID)] = data.Url;
});
Action<MethodLoadUnloadJSTraceData> onJScriptMethodUnload = delegate (MethodLoadUnloadJSTraceData data)
{
codeAddresses.AddMethod(data, sourceFilesByID);
bookKeepingEvent = true;
};
jScriptParser.MethodRuntimeMethodUnload += onJScriptMethodUnload;
jScriptParser.MethodRundownMethodDCStop += onJScriptMethodUnload;
Action<MethodLoadUnloadJSTraceData> onJScriptMethodLoad = delegate (MethodLoadUnloadJSTraceData data)
{
TraceProcess process = processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
process.InsertJITTEDMethod(data.MethodStartAddress, (int)data.MethodSize, delegate ()
{
MethodIndex methodIndex = CodeAddresses.MakeJavaScriptMethod(data, sourceFilesByID);
return new TraceProcess.MethodLookupInfo(data.MethodStartAddress, (int)data.MethodSize, methodIndex);
});
jsJittedMethods.Add((MethodLoadUnloadJSTraceData)data.Clone());
};
jScriptParser.MethodRuntimeMethodLoad += onJScriptMethodLoad;
jScriptParser.MethodRundownMethodDCStart += onJScriptMethodLoad;
// We know that Disk I/O events should never have a stack associated with them (the init events do)
// these sometimes have the same kernel timestamp as CSWITCHs, which cause ambiguity.
kernelParser.AddCallbackForEvents(delegate (DiskIOTraceData data)
{
noStack = true;
});
Action<ClrStackWalkTraceData> clrStackWalk = delegate (ClrStackWalkTraceData data)
{
bookKeepingEvent = true;
// Avoid creating data structures for events we will throw away
if (processingDisabled)
{
return;
}
int i = 0;
// Look for the previous CLR event on this same thread.
for (PastEventInfoIndex prevEventIndex = pastEventInfo.CurrentIndex; ;)
{
i++;
Debug.Assert(i < 20000);
prevEventIndex = pastEventInfo.GetPreviousEventIndex(prevEventIndex, data.ThreadID, true);
if (prevEventIndex == PastEventInfoIndex.Invalid)
{
DebugWarn(false, "Could not find a previous event for a CLR stack trace.", data);
return;
}
if (pastEventInfo.IsClrEvent(prevEventIndex))
{
if (pastEventInfo.HasStack(prevEventIndex))
{
DebugWarn(false, "CLR Stack trying to be given to same event twice (can happen with lost events)", data);
return;
}
pastEventInfo.SetHasStack(prevEventIndex);
var process = Processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, data.TimeStampQPC, process);
CallStackIndex callStackIndex = callStacks.GetStackIndexForStackEvent(
data.InstructionPointers, data.FrameCount, data.PointerSize, thread);
Debug.Assert(callStacks.Depth(callStackIndex) == data.FrameCount);
DebugWarn(pastEventInfo.GetThreadID(prevEventIndex) == data.ThreadID, "Mismatched thread for CLR Stack Trace", data);
// Get the previous event on the same thread.
EventIndex eventIndex = pastEventInfo.GetEventIndex(prevEventIndex);
Debug.Assert(eventIndex != EventIndex.Invalid); // We don't delete CLR events and that is the only way eventIndexes can be invalid
AddStackToEvent(eventIndex, callStackIndex);
pastEventInfo.GetEventCounts(prevEventIndex).m_stackCount++;
return;
}
}
};
rawEvents.Clr.ClrStackWalk += clrStackWalk;
// Process stack trace from EventPipe trace
Action<ClrThreadStackWalkTraceData> clrThreadStackWalk = delegate (ClrThreadStackWalkTraceData data)
{
bookKeepingEvent = true;
// Avoid creating data structures for events we will throw away
if (processingDisabled)
{
return;
}
PastEventInfoIndex prevEventIndex = pastEventInfo.GetPreviousEventIndex(pastEventInfo.CurrentIndex, data.ThreadID, true);
if (prevEventIndex == PastEventInfoIndex.Invalid)
{
DebugWarn(false, "Could not find a previous event for a CLR thread stack trace.", data);
return;
}
var process = Processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, data.TimeStampQPC, process);
CallStackIndex callStackIndex = callStacks.GetStackIndexForStackEvent(
data.InstructionPointers, data.FrameCount, data.PointerSize, thread);
Debug.Assert(callStacks.Depth(callStackIndex) == data.FrameCount);
// Get the previous event and add stack
EventIndex eventIndex = pastEventInfo.GetEventIndex(prevEventIndex);
AddStackToEvent(eventIndex, callStackIndex);
pastEventInfo.GetEventCounts(prevEventIndex).m_stackCount++;
return;
};
var eventPipeParser = new SampleProfilerTraceEventParser(rawEvents);
eventPipeParser.ThreadStackWalk += clrThreadStackWalk;
var clrPrivate = new ClrPrivateTraceEventParser(rawEvents);
clrPrivate.ClrStackWalk += clrStackWalk;
kernelParser.StackWalkStack += delegate (StackWalkStackTraceData data)
{
bookKeepingEvent = true;
if (processingDisabled)
{
return;
}
// Trace.WriteLine("REAL TIME QUEUE: *** STACK EVENT *** " + data.TimeStampRelativeMSec.ToString("f3") + " for event at " + data.EventTimeStampRelativeMSec.ToString("f3"));
var timeStampQPC = data.TimeStampQPC;
IncompleteStack stackInfo = GetIncompleteStackForStackEvent(data, data.EventTimeStampQPC);
TraceProcess process = processes.GetOrCreateProcess(data.ProcessID, timeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, timeStampQPC, process);
var isKernelModeStackFragment = process.IsKernelAddress(data.InstructionPointer(data.FrameCount - 1), data.PointerSize);
if (isKernelModeStackFragment)
{
// If we reach here the fragment we have is totally in the kernel, and thus might have a user mode part that we have
// not seen yet. Thus we have the stackInfo remember this fragment so we can put it together later.
if (stackInfo != null)
{
if (!stackInfo.LogKernelStackFragment(data.InstructionPointers, data.FrameCount, data.PointerSize, timeStampQPC, this))
{
stackInfo.AddEntryToThread(ref thread.lastEntryIntoKernel); // If not done remember to complete it
}
}
}
else
{
// If we reach here, the fragment ends in user mode.
CallStackIndex stackIndex = callStacks.GetStackIndexForStackEvent(
data.InstructionPointers, data.FrameCount, data.PointerSize, thread, CallStackIndex.Invalid);
var lastEmitStackOnExitFromKernelQPC = thread.lastEmitStackOnExitFromKernelQPC;
var loggedUserStack = false; // Have we logged this stack at all
// If this fragment starts in user mode, then we assume that it is on the 'boundary' of kernel and users mode
// and we use this as the 'top' of the stack for all kernel fragments on this thread.
if (!process.IsKernelAddress(data.InstructionPointer(0), data.PointerSize))
{
loggedUserStack = EmitStackOnExitFromKernel(ref thread.lastEntryIntoKernel, stackIndex, stackInfo);
thread.lastEmitStackOnExitFromKernelQPC = data.TimeStampQPC;
}
// If we have not logged the stack of the code above, then log it as a stand alone user stack.
// We don't do this for events that have already been processed by and EmitStackOnExitFromKernelQPC
if (!loggedUserStack && stackInfo != null)
{
if (data.EventTimeStampQPC < lastEmitStackOnExitFromKernelQPC)
{
DebugWarn(false, "Warning: Trying to attach a user stack to a stack already processed by EmitStackOnExitFromKernel. Ignoring data", data);
}
else
{
stackInfo.LogUserStackFragment(stackIndex, this);
}
}
}
};
kernelParser.StackWalkStackKeyKernel += delegate (StackWalkRefTraceData data)
{
bookKeepingEvent = true;
if (processingDisabled)
{
return;
}
IncompleteStack stackInfo = GetIncompleteStackForStackEvent(data, data.EventTimeStampQPC);
if (stackInfo != null)
{
var timeStampQPC = data.TimeStampQPC;
TraceProcess process = processes.GetOrCreateProcess(data.ProcessID, timeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, timeStampQPC, process);
if (!stackInfo.LogKernelStackFragment(data.StackKey, this))
{
stackInfo.AddEntryToThread(ref thread.lastEntryIntoKernel); // If not done remember to complete it
}
}
};
kernelParser.StackWalkStackKeyUser += delegate (StackWalkRefTraceData data)
{
bookKeepingEvent = true;
if (processingDisabled)
{
return;
}
IncompleteStack stackInfo = GetIncompleteStackForStackEvent(data, data.EventTimeStampQPC);
if (stackInfo != null)
{
var timeStampQPC = data.TimeStampQPC;
TraceProcess process = processes.GetOrCreateProcess(data.ProcessID, timeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, timeStampQPC, process);
if (!EmitStackOnExitFromKernel(ref thread.lastEntryIntoKernel, data.StackKey, stackInfo))
{
stackInfo.LogUserStackFragment(data.StackKey, this);
}
}
};
// Matches Delete and Rundown events;
kernelParser.AddCallbackForEvents<StackWalkDefTraceData>(delegate (StackWalkDefTraceData data)
{
bookKeepingEvent = true;
LogStackDefinition(data);
});
// The following 3 callbacks for a small state machine to determine whether the process
// is running server GC and what the server GC threads are.
// We assume we are server GC if there are more than one thread doing the 'MarkHandles' event
// during a GC, and the threads that do that are the server threads. We use this to mark the
// threads as Server GC Threads.
rawEvents.Clr.GCStart += delegate (GCStartTraceData data)
{
var process = Processes.GetProcess(data.ProcessID, data.TimeStampQPC);
if (process == null)
{
return;
}
if ((process.markThreadsInGC.Count == 0) && (process.shouldCheckIsServerGC == false))
{
process.shouldCheckIsServerGC = true;
}
};
rawEvents.Clr.GCStop += delegate (GCEndTraceData data)
{
var process = Processes.GetProcess(data.ProcessID, data.TimeStampQPC);
if (process == null)
{
return;
}
if (process.markThreadsInGC.Count > 0)
{
process.shouldCheckIsServerGC = false;
}
if (!process.isServerGC && (process.markThreadsInGC.Count > 1))
{
process.isServerGC = true;
foreach (var curThread in process.Threads)
{
if (thread.threadInfo == null && process.markThreadsInGC.ContainsKey(curThread.ThreadID))
{
curThread.threadInfo = ".NET Server GC Thread(" + process.markThreadsInGC[curThread.ThreadID] + ")";
}
}
}
};
rawEvents.Clr.GCMarkWithType += delegate (GCMarkWithTypeTraceData data)
{
if (data.Type == (int)MarkRootType.MarkHandles)
{
AddMarkThread(data.ThreadID, data.TimeStampQPC, data.HeapNum);
}
};
clrPrivate.GCMarkHandles += delegate (GCMarkTraceData data)
{
AddMarkThread(data.ThreadID, data.TimeStampQPC, data.HeapNum);
};
var aspNetParser = new AspNetTraceEventParser(rawEvents);
aspNetParser.AspNetReqStart += delegate (AspNetStartTraceData data) { CategorizeThread(data, "Incoming Request Thread"); };
rawEvents.Clr.GCFinalizersStart += delegate (GCNoUserDataTraceData data) { CategorizeThread(data, ".NET Finalizer Thread"); };
rawEvents.Clr.GCFinalizersStop += delegate (GCFinalizersEndTraceData data) { CategorizeThread(data, ".NET Finalizer Thread"); };
Action<TraceEvent> MarkAsBGCThread = delegate (TraceEvent data)
{
var process = Processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, data.TimeStampQPC, process);
bool isServerGC = (thread != null && thread.process.isServerGC);
CategorizeThread(data, ".NET Background GC Thread");
};
// We use more than then GCBGStart to mark a GC thread because we need an event that happens more routinely
// since this might be a circular buffer or other short trace.
clrPrivate.GCBGCStart += delegate (GCNoUserDataTraceData data) { MarkAsBGCThread(data); };
clrPrivate.GCBGC1stConStop += delegate (GCNoUserDataTraceData data) { MarkAsBGCThread(data); };
clrPrivate.GCBGCDrainMark += delegate (BGCDrainMarkTraceData data) { MarkAsBGCThread(data); };
clrPrivate.GCBGCRevisit += delegate (BGCRevisitTraceData data) { MarkAsBGCThread(data); };
rawEvents.Clr.ThreadPoolWorkerThreadAdjustmentSample += delegate (ThreadPoolWorkerThreadAdjustmentSampleTraceData data)
{
CategorizeThread(data, ".NET ThreadPool");
};
rawEvents.Clr.ThreadPoolIODequeue += delegate (ThreadPoolIOWorkTraceData data) { CategorizeThread(data, ".NET IO ThreadPool Worker", true); };
var fxParser = new FrameworkEventSourceTraceEventParser(rawEvents);
fxParser.ThreadPoolDequeueWork += delegate (ThreadPoolDequeueWorkArgs data) { CategorizeThread(data, ".NET ThreadPool Worker"); };
fxParser.ThreadTransferReceive += delegate (ThreadTransferReceiveArgs data) { CategorizeThread(data, ".NET ThreadPool Worker"); };
// Attribute CPU samples to processes.
kernelParser.PerfInfoSample += delegate (SampledProfileTraceData data)
{
if (data.ThreadID == 0 && !data.NonProcess && !(options != null && options.KeepAllEvents)) // Don't count process 0 (idle) unless they are executing DPCs or ISRs.
{
removeFromStream = true;
return;
}
var process = Processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
thread = Threads.GetOrCreateThread(data.ThreadID, data.TimeStampQPC, process);
thread.cpuSamples++;
};
// We assume that the sampling interval is uniform over the trace. We pick the start if it
// is there, otherwise the OLD value of the LAST set interval (since we RESET the interval at the end)
// OR the OLD value at the end.
bool setSeen = false;
bool startSeen = false;
kernelParser.PerfInfoCollectionStart += delegate (SampledProfileIntervalTraceData data)
{
if (data.SampleSource != 0) // 0 is the CPU sampling interval
{
return;
}
startSeen = true;
sampleProfileInterval100ns = data.NewInterval;
};
kernelParser.PerfInfoSetInterval += delegate (SampledProfileIntervalTraceData data)
{
if (data.SampleSource != 0) // 0 is the CPU sampling interval
{
return;
}
setSeen = true;
if (!startSeen)
{
sampleProfileInterval100ns = data.OldInterval;
}
};
kernelParser.PerfInfoSetInterval += delegate (SampledProfileIntervalTraceData data)
{
if (data.SampleSource != 0) // 0 is the CPU sampling interval
{
return;
}
if (!setSeen && !startSeen)
{
sampleProfileInterval100ns = data.OldInterval;
}
};
}