in src/EtwHeapDump/DotNetHeapDumpGraphReader.cs [76:500]
internal void SetupCallbacks(MemoryGraph memoryGraph, TraceEventDispatcher source, string processNameOrId = null, double startTimeRelativeMSec = 0)
{
m_graph = memoryGraph;
m_typeID2TypeIndex = new Dictionary<Address, NodeTypeIndex>(1000);
m_moduleID2Name = new Dictionary<Address, string>(16);
m_arrayNametoIndex = new Dictionary<string, NodeTypeIndex>(32);
m_objectToRCW = new Dictionary<Address, RCWInfo>(100);
m_nodeBlocks = new Queue<GCBulkNodeTraceData>();
m_edgeBlocks = new Queue<GCBulkEdgeTraceData>();
m_typeBlocks = new Queue<GCBulkTypeTraceData>();
m_staticVarBlocks = new Queue<GCBulkRootStaticVarTraceData>();
m_ccwBlocks = new Queue<GCBulkRootCCWTraceData>();
m_typeIntern = new Dictionary<string, NodeTypeIndex>();
m_root = new MemoryNodeBuilder(m_graph, "[.NET Roots]");
m_typeStorage = m_graph.AllocTypeNodeStorage();
// We also keep track of the loaded modules in the target process just in case it is a project N scenario.
// (Not play for play but it is small).
m_modules = new Dictionary<Address, Module>(32);
m_ignoreEvents = true;
m_ignoreUntilMSec = startTimeRelativeMSec;
m_processId = 0; // defaults to a wildcard.
if (processNameOrId != null)
{
if (!int.TryParse(processNameOrId, out m_processId))
{
m_processId = -1; // an illegal value.
m_processName = processNameOrId;
}
}
// Remember the module IDs too.
Action<ModuleLoadUnloadTraceData> moduleCallback = delegate (ModuleLoadUnloadTraceData data)
{
if (data.ProcessID != m_processId)
{
return;
}
if (!m_moduleID2Name.ContainsKey((Address)data.ModuleID))
{
m_moduleID2Name[(Address)data.ModuleID] = data.ModuleILPath;
}
m_log.WriteLine("Found Module {0} ID 0x{1:x}", data.ModuleILFileName, (Address)data.ModuleID);
};
source.Clr.AddCallbackForEvents<ModuleLoadUnloadTraceData>(moduleCallback); // Get module events for clr provider
// TODO should not be needed if we use CAPTURE_STATE when collecting.
var clrRundown = new ClrRundownTraceEventParser(source);
clrRundown.AddCallbackForEvents<ModuleLoadUnloadTraceData>(moduleCallback); // and its rundown provider.
DbgIDRSDSTraceData lastDbgData = null;
var symbolParser = new SymbolTraceEventParser(source);
symbolParser.ImageIDDbgID_RSDS += delegate (DbgIDRSDSTraceData data)
{
if (data.ProcessID != m_processId)
{
return;
}
lastDbgData = (DbgIDRSDSTraceData)data.Clone();
};
source.Kernel.ImageGroup += delegate (ImageLoadTraceData data)
{
if (m_processId == 0)
{
return;
}
if (data.ProcessID != m_processId)
{
return;
}
Module module = new Module(data.ImageBase);
module.Path = data.FileName;
module.Size = data.ImageSize;
module.BuildTime = data.BuildTime;
if (lastDbgData != null && data.TimeStampRelativeMSec == lastDbgData.TimeStampRelativeMSec)
{
module.PdbGuid = lastDbgData.GuidSig;
module.PdbAge = lastDbgData.Age;
module.PdbName = lastDbgData.PdbFileName;
}
m_modules[module.ImageBase] = module;
};
// TODO this does not work in the circular case
source.Kernel.ProcessGroup += delegate (ProcessTraceData data)
{
if (0 <= m_processId || m_processName == null)
{
return;
}
if (string.Compare(data.ProcessName, processNameOrId, StringComparison.OrdinalIgnoreCase) == 0)
{
m_log.WriteLine("Found process id {0} for process Name {1}", processNameOrId, data.ProcessName);
m_processId = data.ProcessID;
}
else
{
m_log.WriteLine("Found process {0} but does not match {1}", data.ProcessName, processNameOrId);
}
};
source.Clr.GCGenAwareStart += delegate (GenAwareBeginTraceData data)
{
m_seenStart = true;
m_ignoreEvents = false;
};
source.Clr.GCStart += delegate (GCStartTraceData data)
{
// If this GC is not part of a heap dump, ignore it.
// TODO FIX NOW if (data.ClientSequenceNumber == 0)
// return;
if (data.TimeStampRelativeMSec < m_ignoreUntilMSec)
{
return;
}
if (m_processId == 0)
{
m_processId = data.ProcessID;
m_log.WriteLine("Process wildcard selects process id {0}", m_processId);
}
if (data.ProcessID != m_processId)
{
m_log.WriteLine("GC Start found but Process ID {0} != {1} desired ID", data.ProcessID, m_processId);
return;
}
if (!IsProjectN && data.ProviderGuid == ClrTraceEventParser.NativeProviderGuid)
{
IsProjectN = true;
}
if (data.Depth < 2 || data.Type != GCType.NonConcurrentGC)
{
m_log.WriteLine("GC Start found but not a Foreground Gen 2 GC");
return;
}
if (data.Reason != GCReason.Induced)
{
m_log.WriteLine("GC Start not induced. Skipping.");
return;
}
if (!m_seenStart)
{
m_gcID = data.Count;
m_log.WriteLine("Found a Gen2 Induced non-background GC Start at {0:n3} msec GC Count {1}", data.TimeStampRelativeMSec, m_gcID);
m_ignoreEvents = false;
m_seenStart = true;
memoryGraph.Is64Bit = (data.PointerSize == 8);
}
};
source.Clr.GCStop += delegate (GCEndTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
if (data.Count == m_gcID)
{
m_log.WriteLine("Found a GC Stop at {0:n3} for GC {1}, ignoring events from now on.", data.TimeStampRelativeMSec, m_gcID);
m_ignoreEvents = true;
if (m_nodeBlocks.Count == 0 && m_typeBlocks.Count == 0 && m_edgeBlocks.Count == 0)
{
m_log.WriteLine("Found no node events, looking for another GC");
m_seenStart = false;
return;
}
// TODO we have to continue processing to get the module rundown events.
// If we could be sure to get these early, we could optimized this.
// source.StopProcessing();
}
else
{
m_log.WriteLine("Found a GC Stop at {0:n3} but id {1} != {2} Target ID", data.TimeStampRelativeMSec, data.Count, m_gcID);
}
};
source.Clr.GCGenAwareEnd += delegate (GenAwareEndTraceData data)
{
m_ignoreEvents = true;
if (m_nodeBlocks.Count == 0 && m_typeBlocks.Count == 0 && m_edgeBlocks.Count == 0)
{
m_log.WriteLine("Found no node events, looking for another GC");
m_seenStart = false;
return;
}
};
source.Clr.TypeBulkType += delegate (GCBulkTypeTraceData data)
{
// Don't check m_ignoreEvents here, as BulkType events can be emitted by other events...such as the GC allocation event.
// This means that when setting m_processId to 0 in the command line may still lose type events.
if (data.ProcessID != m_processId)
{
return;
}
m_typeBlocks.Enqueue((GCBulkTypeTraceData)data.Clone());
};
source.Clr.GCBulkNode += delegate (GCBulkNodeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
m_nodeBlocks.Enqueue((GCBulkNodeTraceData)data.Clone());
};
source.Clr.GCBulkEdge += delegate (GCBulkEdgeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
m_edgeBlocks.Enqueue((GCBulkEdgeTraceData)data.Clone());
};
source.Clr.GCBulkRootEdge += delegate (GCBulkRootEdgeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
MemoryNodeBuilder staticRoot = m_root.FindOrCreateChild("[static vars]");
for (int i = 0; i < data.Count; i++)
{
var value = data.Values(i);
var flags = value.GCRootFlag;
if ((flags & GCRootFlags.WeakRef) == 0) // ignore weak references. they are not roots.
{
GCRootKind kind = value.GCRootKind;
MemoryNodeBuilder root = m_root;
string name;
if (kind == GCRootKind.Stack)
{
name = "[local vars]";
}
else
{
root = m_root.FindOrCreateChild("[other roots]");
if ((flags & GCRootFlags.RefCounted) != 0)
{
name = "[COM/WinRT Objects]";
}
else if (kind == GCRootKind.Finalizer)
{
name = "[finalizer Handles]";
}
else if (kind == GCRootKind.Handle)
{
if (flags == GCRootFlags.Pinning)
{
name = "[pinning Handles]";
}
else
{
name = "[strong Handles]";
}
}
else
{
name = "[other Handles]";
}
// Remember the root for later processing.
if (value.RootedNodeAddress != 0)
{
Address gcRootId = value.GCRootID;
if (gcRootId != 0 && IsProjectN)
{
Module gcRootModule = GetModuleForAddress(gcRootId);
if (gcRootModule != null)
{
var staticRva = (int)(gcRootId - gcRootModule.ImageBase);
var staticTypeIdx = m_graph.CreateType(staticRva, gcRootModule, 0, " (static var)");
var staticNodeIdx = m_graph.CreateNode();
m_children.Clear();
m_children.Add(m_graph.GetNodeIndex(value.RootedNodeAddress));
m_graph.SetNode(staticNodeIdx, staticTypeIdx, 0, m_children);
staticRoot.AddChild(staticNodeIdx);
Trace.WriteLine("Got Static 0x" + gcRootId.ToString("x") + " pointing at 0x" + value.RootedNodeAddress.ToString("x") + " kind " + value.GCRootKind + " flags " + value.GCRootFlag);
continue;
}
}
Trace.WriteLine("Got GC Root 0x" + gcRootId.ToString("x") + " pointing at 0x" + value.RootedNodeAddress.ToString("x") + " kind " + value.GCRootKind + " flags " + value.GCRootFlag);
}
}
root = root.FindOrCreateChild(name);
Address objId = value.RootedNodeAddress;
root.AddChild(m_graph.GetNodeIndex(objId));
}
}
};
source.Clr.GCBulkRCW += delegate (GCBulkRCWTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
for (int i = 0; i < data.Count; i++)
{
GCBulkRCWValues comInfo = data.Values(i);
m_objectToRCW[comInfo.ObjectID] = new RCWInfo(comInfo);
}
};
source.Clr.GCBulkRootCCW += delegate (GCBulkRootCCWTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
m_ccwBlocks.Enqueue((GCBulkRootCCWTraceData)data.Clone());
};
source.Clr.GCBulkRootStaticVar += delegate (GCBulkRootStaticVarTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
m_staticVarBlocks.Enqueue((GCBulkRootStaticVarTraceData)data.Clone());
};
source.Clr.GCBulkRootConditionalWeakTableElementEdge += delegate (GCBulkRootConditionalWeakTableElementEdgeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
var otherRoots = m_root.FindOrCreateChild("[other roots]");
var dependentHandles = otherRoots.FindOrCreateChild("[Dependent Handles]");
for (int i = 0; i < data.Count; i++)
{
var value = data.Values(i);
// TODO fix this so that they you see this as an arc from source to target.
// The target is alive only if the source ID (which is a weak handle) is alive (non-zero)
if (value.GCKeyNodeID != 0)
{
dependentHandles.AddChild(m_graph.GetNodeIndex(value.GCValueNodeID));
}
}
};
source.Clr.GCGenerationRange += delegate (GCGenerationRangeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
if (m_dotNetHeapInfo == null)
{
return;
}
// We want the 'after' ranges so we wait
if (m_nodeBlocks.Count == 0)
{
return;
}
Address start = data.RangeStart;
Address end = start + data.RangeUsedLength;
if (m_dotNetHeapInfo.Segments == null)
{
m_dotNetHeapInfo.Segments = new List<GCHeapDumpSegment>();
}
GCHeapDumpSegment segment = new GCHeapDumpSegment();
segment.Start = start;
segment.End = end;
switch (data.Generation)
{
case 0:
segment.Gen0End = end;
break;
case 1:
segment.Gen1End = end;
break;
case 2:
segment.Gen2End = end;
break;
case 3:
segment.Gen3End = end;
break;
case 4:
segment.Gen4End = end;
break;
default:
throw new Exception("Invalid generation in GCGenerationRangeTraceData");
}
m_dotNetHeapInfo.Segments.Add(segment);
};
}