private unsafe void SetupCallbacks()

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;
                }
            };
        }