/// <summary> /// This demo shows you how to create a a graph of memory and turn it into a stackSource with MemoryGraphStackSource. /// </summary> public void DemoMemoryGraph() { // Make a custom stack source that was created out of nothing. InternStackSouce is your friend here. MemoryGraph myGraph = new MemoryGraph(1000); // Create a memory graph out of 'nothing'. In the example below we create a graph where the root which points at // 'base' which has a bunch of children, each of which have a child that points back to the base node (thus it has lots of cycles) var baseIdx = myGraph.GetNodeIndex(0); // Define the NAME (index) for the of the graph (but we have not defined what is in it) GrowableArray <NodeIndex> children = new GrowableArray <NodeIndex>(1); // This array is reused again and again for each child. GrowableArray <NodeIndex> rootChildren = new GrowableArray <NodeIndex>(100); // This is used to create the children of the root; //Here I make up a graph of memory addresses at made up locations for (Address objAddr = 0x100000; objAddr < 0x200000; objAddr += 0x10000) { NodeIndex nodeIdx = myGraph.GetNodeIndex(objAddr); // Create the name (node index) for the child // Make a type for the child. In this case we make a new type for each node, normally you keep these in a interning table so that // every distinct type name has exactly one type index. Interning is not STRICTLLY needed, but the representation assumes that // there are many more nodes than there are types. NodeTypeIndex nodeTypeIdx = myGraph.CreateType("MyType_" + objAddr.ToString(), "MyModule"); // Create a list of children for this node in this case, each node has exactly one child, which is the root node. children.Clear(); children.Add(baseIdx); // Actually define the node with the given name (nodeIdx), type, size (100 in our case) and children; myGraph.SetNode(nodeIdx, nodeTypeIdx, 100, children); rootChildren.Add(nodeIdx); // Remember this node name as belonging to the things that the root points at. } // At this point we have everything we need to define the base node, do it here. myGraph.SetNode(baseIdx, myGraph.CreateType("[Base]"), 0, rootChildren); // Create a root node that points at the base node. myGraph.RootIndex = myGraph.GetNodeIndex(1); children.Clear(); children.Add(baseIdx); myGraph.SetNode(myGraph.RootIndex, myGraph.CreateType("[ROOT]"), 0, children); // Note that the raw graph APIs force you to know all the children of a particular node before you can define // the node. There is a class call MemoryNodeBuilder which makes this a bit nicer in that you can incrementally // add children to nodes and then 'finalize' them all at once at the end. Ideally, however you don't have many // nodes that need MemoryNodeBuilder as they are more expensive to build. // So far, the graph is in 'write mode' where only creation APIs are allowed. Change to 'Read Mode' which no writes // are allowed by read APIs are allowed. (We may lift this restriction at some point). myGraph.AllowReading(); // I can dump it as XML to look at it. using (var writer = File.CreateText("MyGraph.dump.xml")) { myGraph.WriteXml(writer); } // I can write the graph out as a file and read it back in later. // myGraph.WriteAsBinaryFile("myGraph.gcGraph"); // var roundTrip = MemoryGraph.ReadFromBinaryFile("myGraph.gcGraph"); // OK we have a graph, turn it into a stack source so that I can view it. MemoryGraphStackSource myStackSource = new MemoryGraphStackSource(myGraph, LogFile); // Create a Stacks class (which remembers all the Filters, Scaling policy and other things the viewer wants. Stacks stacks = new Stacks(myStackSource); // If you wanted to, you can change the filtering ... stacks.Filter.GroupRegExs = ""; stacks.Filter.MinInclusiveTimePercent = ""; // And view it. OpenStackViewer(stacks); }
/// <summary> /// Reads the Nodes Element /// </summary> private static void ReadNodesFromXml(XmlReader reader, MemoryGraph graph) { Debug.Assert(reader.NodeType == XmlNodeType.Element); var inputDepth = reader.Depth; reader.Read(); // Advance to children var children = new GrowableArray <NodeIndex>(1000); var typeStorage = graph.AllocTypeNodeStorage(); while (inputDepth < reader.Depth) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "Node": { NodeIndex readNodeIndex = (NodeIndex)FetchInt(reader, "Index", -1); NodeTypeIndex typeIndex = (NodeTypeIndex)FetchInt(reader, "TypeIndex", -1); int size = FetchInt(reader, "Size"); if (readNodeIndex == NodeIndex.Invalid) { throw new ApplicationException("Node element does not have a Index attribute."); } if (typeIndex == NodeTypeIndex.Invalid) { throw new ApplicationException("Node element does not have a TypeIndex attribute."); } // TODO FIX NOW very inefficient. Use ReadValueChunk and FastStream to make more efficient. children.Clear(); var body = reader.ReadElementContentAsString(); foreach (var num in Regex.Split(body, @"\s+")) { if (num.Length > 0) { children.Add((NodeIndex)int.Parse(num)); } } if (size == 0) { size = graph.GetType(typeIndex, typeStorage).Size; } // TODO should probably just reserve node index 0 to be an undefined object? NodeIndex nodeIndex = 0; if (readNodeIndex != 0) { nodeIndex = graph.CreateNode(); } if (readNodeIndex != nodeIndex) { throw new ApplicationException("Node Indexes do not start at 0 or 1 and increase consecutively."); } graph.SetNode(nodeIndex, typeIndex, size, children); } break; default: Debug.WriteLine("Skipping unknown element {0}", reader.Name); reader.Skip(); break; } } else if (!reader.Read()) { break; } } }
void Read(TextReader reader) { var framePattern = new Regex(@"\b(\w+?)\!(\S\(?[\S\s]*\)?)"); var stackStart = new Regex(@"Call Site"); // the call stack from the debugger kc command looksl like this //Call Site //coreclr!JIT_MonEnterWorker_Portable //System_Windows_ni!MS.Internal.ManagedPeerTable.TryGetManagedPeer(IntPtr, Boolean, System.Object ByRef) //System_Windows_ni!MS.Internal.ManagedPeerTable.EnsureManagedPeer(IntPtr, Int32, System.Type, Boolean) //System_Windows_ni!MS.Internal.FrameworkCallbacks.CheckPeerType(IntPtr, System.String, Boolean) //System_Windows_ni!DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int32, IntPtr, Int32) //coreclr!UM2MThunk_WrapperHelper //coreclr!UMThunkStubWorker //coreclr!UMThunkStub //agcore!CParser::StartObjectElement //agcore!CParser::Attribute //agcore!CParser::LoadXaml var stack = new GrowableArray <DebuggerCallStackFrame>(); bool newCallStackFound = false; var sample = new StackSourceSample(this); float time = 0; for (; ;) { var line = reader.ReadLine(); if (line == null) { break; } var match = framePattern.Match(line); if (match.Success && newCallStackFound) { var module = match.Groups[1].Value; var methodName = match.Groups[2].Value; // trim the methodName if it has file name info (if the trace is collected with kv instead of kc) int index = methodName.LastIndexOf(")+"); if (index != -1) { methodName = methodName.Substring(0, index + 1); } var moduleIndex = Interner.ModuleIntern(module); var frameIndex = Interner.FrameIntern(methodName, moduleIndex); DebuggerCallStackFrame frame = new DebuggerCallStackFrame(); frame.frame = frameIndex; stack.Add(frame); } else { var stackStartMatch = stackStart.Match(line); if (stackStartMatch.Success) { // start a new sample. // add the previous sample // clear the stack if (stack.Count != 0) { StackSourceCallStackIndex parent = StackSourceCallStackIndex.Invalid; for (int i = stack.Count - 1; i >= 0; --i) { parent = Interner.CallStackIntern(stack[i].frame, parent); } stack.Clear(); sample.StackIndex = parent; sample.TimeRelativeMSec = time; time++; AddSample(sample); } newCallStackFound = true; } } } Interner.DoneInterning(); }
public void AddSample(LinuxPerfScriptStackSourceSample sample) { var baseSample = AddSample((StackSourceSample)sample); m_LinuxPerfScriptSamples.Add(new LinuxPerfScriptStackSourceSample(baseSample, sample.CpuNumber)); }
/// <summary> /// After reading the events the graph is not actually created, you need to post process the information we gathered /// from the events. This is where that happens. Thus 'SetupCallbacks, Process(), ConvertHeapDataToGraph()' is how /// you dump a heap. /// </summary> internal unsafe void ConvertHeapDataToGraph() { int maxNodeCount = 10_000_000; if (m_converted) { return; } m_converted = true; if (!m_seenStart) { if (m_processName != null) { throw new ApplicationException("ETL file did not include a Heap Dump for process " + m_processName); } throw new ApplicationException("ETL file did not include a Heap Dump for process ID " + m_processId); } if (!m_ignoreEvents) { throw new ApplicationException("ETL file shows the start of a heap dump but not its completion."); } m_log.WriteLine("Processing Heap Data, BulkTypeEventCount:{0} BulkNodeEventCount:{1} BulkEdgeEventCount:{2}", m_typeBlocks.Count, m_nodeBlocks.Count, m_edgeBlocks.Count); // Process the type information (we can't do it on the fly because we need the module information, which may be // at the end of the trace. while (m_typeBlocks.Count > 0) { GCBulkTypeTraceData data = m_typeBlocks.Dequeue(); for (int i = 0; i < data.Count; i++) { GCBulkTypeValues typeData = data.Values(i); var typeName = typeData.TypeName; if (IsProjectN) { // For project N we only log the type ID and module base address. Debug.Assert(typeName.Length == 0); Debug.Assert((typeData.Flags & TypeFlags.ModuleBaseAddress) != 0); var moduleBaseAddress = typeData.TypeID - (ulong)typeData.TypeNameID; // Tricky way of getting the image base. Debug.Assert((moduleBaseAddress & 0xFFFF) == 0); // Image loads should be on 64K boundaries. Module module = GetModuleForImageBase(moduleBaseAddress); if (module.Path == null) { m_log.WriteLine("Error: Could not find DLL name for imageBase 0x{0:x} looking up typeID 0x{1:x} with TypeNameID {2:x}", moduleBaseAddress, typeData.TypeID, typeData.TypeNameID); } m_typeID2TypeIndex[typeData.TypeID] = m_graph.CreateType(typeData.TypeNameID, module); } else { if (typeName.Length == 0) { if ((typeData.Flags & TypeFlags.Array) != 0) { typeName = "ArrayType(0x" + typeData.TypeNameID.ToString("x") + ")"; } else { typeName = "Type(0x" + typeData.TypeNameID.ToString("x") + ")"; } } // TODO FIX NOW these are kind of hacks typeName = Regex.Replace(typeName, @"`\d+", ""); typeName = typeName.Replace("[", "<"); typeName = typeName.Replace("]", ">"); typeName = typeName.Replace("<>", "[]"); string moduleName; if (!m_moduleID2Name.TryGetValue(typeData.ModuleID, out moduleName)) { moduleName = "Module(0x" + typeData.ModuleID.ToString("x") + ")"; m_moduleID2Name[typeData.ModuleID] = moduleName; } // Is this type a an RCW? If so mark the type name that way. if ((typeData.Flags & TypeFlags.ExternallyImplementedCOMObject) != 0) { typeName = "[RCW " + typeName + "]"; } m_typeID2TypeIndex[typeData.TypeID] = CreateType(typeName, moduleName); // Trace.WriteLine(string.Format("Type 0x{0:x} = {1}", typeData.TypeID, typeName)); } } } // Process all the ccw root information (which also need the type information complete) var ccwRoot = m_root.FindOrCreateChild("[COM/WinRT Objects]"); while (m_ccwBlocks.Count > 0) { GCBulkRootCCWTraceData data = m_ccwBlocks.Dequeue(); GrowableArray <NodeIndex> ccwChildren = new GrowableArray <NodeIndex>(1); for (int i = 0; i < data.Count; i++) { unsafe { GCBulkRootCCWValues ccwInfo = data.Values(i); // TODO Debug.Assert(ccwInfo.IUnknown != 0); if (ccwInfo.IUnknown == 0) { // TODO currently there are times when a CCWs IUnknown pointer is not set (it is set lazily). // m_log.WriteLine("Warning seen a CCW with IUnknown == 0"); continue; } // Create a CCW node that represents the COM object that has one child that points at the managed object. var ccwNode = m_graph.GetNodeIndex(ccwInfo.IUnknown); var ccwTypeIndex = GetTypeIndex(ccwInfo.TypeID, 200); var ccwType = m_graph.GetType(ccwTypeIndex, m_typeStorage); var typeName = "[CCW 0x" + ccwInfo.IUnknown.ToString("x") + " for type " + ccwType.Name + "]"; ccwTypeIndex = CreateType(typeName); ccwChildren.Clear(); ccwChildren.Add(m_graph.GetNodeIndex(ccwInfo.ObjectID)); m_graph.SetNode(ccwNode, ccwTypeIndex, 200, ccwChildren); ccwRoot.AddChild(ccwNode); } } } // Process all the static variable root information (which also need the module information complete var staticVarsRoot = m_root.FindOrCreateChild("[static vars]"); while (m_staticVarBlocks.Count > 0) { GCBulkRootStaticVarTraceData data = m_staticVarBlocks.Dequeue(); for (int i = 0; i < data.Count; i++) { GCBulkRootStaticVarValues staticVarData = data.Values(i); var rootToAddTo = staticVarsRoot; if ((staticVarData.Flags & GCRootStaticVarFlags.ThreadLocal) != 0) { rootToAddTo = m_root.FindOrCreateChild("[thread static vars]"); } // Get the type name. NodeTypeIndex typeIdx; string typeName; if (m_typeID2TypeIndex.TryGetValue(staticVarData.TypeID, out typeIdx)) { var type = m_graph.GetType(typeIdx, m_typeStorage); typeName = type.Name; } else { typeName = "Type(0x" + staticVarData.TypeID.ToString("x") + ")"; } string fullFieldName = typeName + "." + staticVarData.FieldName; rootToAddTo = rootToAddTo.FindOrCreateChild("[static var " + fullFieldName + "]"); var nodeIdx = m_graph.GetNodeIndex(staticVarData.ObjectID); rootToAddTo.AddChild(nodeIdx); } } // var typeStorage = m_graph.AllocTypeNodeStorage(); GCBulkNodeUnsafeNodes nodeStorage = new GCBulkNodeUnsafeNodes(); // Process all the node and edge nodes we have collected. bool doCompletionCheck = true; for (; ;) { GCBulkNodeUnsafeNodes *node = GetNextNode(&nodeStorage); if (node == null) { break; } // Get the node index var nodeIdx = m_graph.GetNodeIndex((Address)node->Address); var objSize = (int)node->Size; Debug.Assert(node->Size < 0x1000000000); var typeIdx = GetTypeIndex(node->TypeID, objSize); // TODO FIX NOW REMOVE // var type = m_graph.GetType(typeIdx, typeStorage); // Trace.WriteLine(string.Format("Got Object 0x{0:x} Type {1} Size {2} #children {3} nodeIdx {4}", (Address)node->Address, type.Name, objSize, node->EdgeCount, nodeIdx)); // Process the edges (which can add children) m_children.Clear(); for (int i = 0; i < node->EdgeCount; i++) { Address edge = GetNextEdge(); var childIdx = m_graph.GetNodeIndex(edge); m_children.Add(childIdx); // Trace.WriteLine(string.Format(" Child 0x{0:x}", edge)); } // TODO we can use the nodes type to see if this is an RCW before doing this lookup which may be a bit more efficient. RCWInfo info; if (m_objectToRCW.TryGetValue((Address)node->Address, out info)) { // Add the COM object this RCW points at as a child of this node. m_children.Add(m_graph.GetNodeIndex(info.IUnknown)); // We add 1000 to account for the overhead of the RCW that is NOT on the GC heap. objSize += 1000; } Debug.Assert(!m_graph.IsDefined(nodeIdx)); m_graph.SetNode(nodeIdx, typeIdx, objSize, m_children); if (m_graph.NodeCount >= maxNodeCount) { doCompletionCheck = false; var userMessage = string.Format("Exceeded max node count {0}", maxNodeCount); m_log.WriteLine("[WARNING: ]", userMessage); break; } } if (doCompletionCheck && m_curEdgeBlock != null && m_curEdgeBlock.Count != m_curEdgeIdx) { throw new ApplicationException("Error: extra edge data. Giving up on heap dump."); } m_root.Build(); m_graph.RootIndex = m_root.Index; }
private void Read(Stream rawStream) { XmlReaderSettings settings = new XmlReaderSettings() { IgnoreWhitespace = true, IgnoreComments = true }; XmlReader reader = XmlTextReader.Create(rawStream, settings); var stack = new GrowableArray <StackSourceSample>(); bool metricsInclusive = false; // If true, we need to convert them to exclusive as part of processing while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (reader.Name == "node") { var sample = new StackSourceSample(this); string callTree = reader.GetAttribute("call_tree"); // Case for allocation stacks string sizeStr = reader.GetAttribute("size"); if (sizeStr != null) { metricsInclusive = true; // allocation numbers are inclusive int size = 0; int.TryParse(sizeStr, out size); sample.Metric = size; string recoredObectsStr = reader.GetAttribute("recorded_objects"); int recoredObects = 0; if (recoredObectsStr != null) { int.TryParse(recoredObectsStr, out recoredObects); } sample.Count = recoredObects; } else { Debug.Assert(metricsInclusive == false); // CPU time is exclusive. // For CPU string own_time_msStr = reader.GetAttribute("own_time_ms"); if (own_time_msStr != null) { int own_time_ms; int.TryParse(own_time_msStr, out own_time_ms); sample.Metric = own_time_ms; string countStr = reader.GetAttribute("count"); int count = 0; if (countStr != null) { int.TryParse(countStr, out count); } sample.Count = count; } } // Get the parent stack for this line var parentStackIndex = StackSourceCallStackIndex.Invalid; int depth = stack.Count; if (depth > 0) { StackSourceSample parent = stack[depth - 1]; parentStackIndex = parent.StackIndex; if (metricsInclusive) { // The values are inclusive, but StackSoruceSamples are the exclusive amounts, so remove children. parent.Count -= sample.Count; parent.Metric -= sample.Metric; } } if (callTree != null) { var frameIndex = Interner.FrameIntern(callTree); sample.StackIndex = Interner.CallStackIntern(frameIndex, parentStackIndex); } stack.Add(sample); } } if (reader.NodeType == XmlNodeType.EndElement || reader.IsEmptyElement) { if (reader.Name == "node") { StackSourceSample sample = stack.Pop(); if ((sample.Count > 0 || sample.Metric > 0) && sample.StackIndex != StackSourceCallStackIndex.Invalid) { AddSample(sample); } } } } Debug.Assert(stack.Count == 0); Interner.DoneInterning(); }
void Read(TextReader reader) { // TODO this is relatively inefficient. var regEx = new Regex(@"^\s*(\d+)\s*(\d+)\s*\[\s*(\d+)\s*\]\s*(\S*?)!?(.*)"); var stack = new GrowableArray <WTStackElem>(); WTStackElem elem = new WTStackElem(); long time = 0; for (; ;) { var line = reader.ReadLine(); if (line == null) { break; } var match = regEx.Match(line); if (match.Success) { int excInstrSoFar = int.Parse(match.Groups[1].Value); int depth = int.Parse(match.Groups[3].Value); string module = match.Groups[4].Value; string method = match.Groups[5].Value; var moduleIndex = ModuleIntern(module); var frameIndex = FrameIntern(method, moduleIndex); var parent = StackSourceCallStackIndex.Invalid; if (depth > 0) { parent = stack[depth - 1].FirstCallStackIndex; // TODO handle out of range } var callStackIndex = CallStackIntern(frameIndex, parent); int extra = stack.Count - depth; int exclInstr; if (extra > 0) { elem = stack[depth]; if (callStackIndex == elem.CallStackIndex) { exclInstr = excInstrSoFar - elem.ExclInstrSoFar; } else { exclInstr = excInstrSoFar; elem.CallStackIndex = callStackIndex; } Debug.Assert(exclInstr >= 0); stack.RemoveRange(depth, extra); } else { elem.CallStackIndex = callStackIndex; elem.FirstCallStackIndex = callStackIndex; Debug.Assert(extra == 0); exclInstr = excInstrSoFar; } elem.ExclInstrSoFar = excInstrSoFar; stack.Add(elem); time += exclInstr; var sample = new StackSourceSample(this); sample.SampleIndex = (StackSourceSampleIndex)m_samples.Count; sample.Metric = exclInstr; sample.TimeRelMSec = time - exclInstr; sample.StackIndex = elem.FirstCallStackIndex; // Break long sequences of instructions into individual samples. This // makes timeline work well. // TODO this bloats the data, not clear if this is the right tradeoff .... const int maxSize = 20; while (sample.Metric > maxSize) { var subSample = new StackSourceSample(sample); subSample.Metric = maxSize; sample.Metric -= maxSize; sample.TimeRelMSec += maxSize; m_samples.Add(subSample); sample.SampleIndex = (StackSourceSampleIndex)m_samples.Count; } m_samples.Add(sample); #if DEBUG var sampleStr = this.ToString(sample); Debug.WriteLine(sampleStr); #endif } } m_sampleTimeRelMSecLimit = time; CompletedReading(); }
void Read(TextReader reader) { // TODO this is relatively inefficient. var regEx = new Regex(@"^\s*(\d+)\s*(\d+)\s*\[\s*(\d+)\s*\]\s*(\S*?)!?(.*)"); var stack = new GrowableArray <WTStackElem>(); WTStackElem elem = new WTStackElem(); long time = 0; var sample = new StackSourceSample(this); for (; ;) { var line = reader.ReadLine(); if (line == null) { break; } var match = regEx.Match(line); if (match.Success) { // Parse the line. int excInstrSoFar = int.Parse(match.Groups[1].Value); int depth = int.Parse(match.Groups[3].Value); string module = match.Groups[4].Value; string method = match.Groups[5].Value; // Form the name for this line var moduleIndex = Interner.ModuleIntern(module); var frameIndex = Interner.FrameIntern(method, moduleIndex); // Get the parent stack for this line var parent = StackSourceCallStackIndex.Invalid; if (depth > 0) { parent = stack[depth - 1].FirstCallStackIndex; // TODO handle out of range } // Form the stack for this entry var callStackIndex = Interner.CallStackIntern(frameIndex, parent); int exclInstr; // Number of instructions executed on this line int extra = stack.Count - depth; // The number of frames we need to pop off (including me) if (extra > 0) { // We returned from one or more methods OR we have not left the current method // elem = stack[depth]; // We expect to return to the same method we were at at this depth. if (callStackIndex == elem.CallStackIndex) { exclInstr = excInstrSoFar - elem.ExclInstrSoFar; // We are continuing the function } else { // We are tail-calling to another routine. exclInstr = excInstrSoFar; elem.CallStackIndex = callStackIndex; } // Pop off all the frames we returned from Debug.Assert(exclInstr >= 0); stack.RemoveRange(depth, extra); } else { // Means we are adding a new frame (we called someone) Debug.Assert(extra == 0); // We always add only one more frame (e.g. we never go from depth 2 to 4) elem.CallStackIndex = callStackIndex; elem.FirstCallStackIndex = callStackIndex; exclInstr = excInstrSoFar; } elem.ExclInstrSoFar = excInstrSoFar; stack.Add(elem); time += exclInstr; sample.Metric = exclInstr; sample.TimeRelativeMSec = time - exclInstr; sample.StackIndex = elem.FirstCallStackIndex; AddSample(sample); } } Interner.DoneInterning(); }
public GCHeapGraph(GCHeap heap) : base((int)heap.NumberOfObjects) { // TODO is basically nodes that have not had 'SetNode' done to them. Thus the node index has been // allocated, but we have not processed the defintion of the node. // This list should NEVER contain duplicates (you should test if you already processed the node before // adding to this queue. Queue <NodeIndex> toDo = new Queue <NodeIndex>(); // Create the root node. var root = new MemoryNodeBuilder(this, "[root]"); // Create categories for the roots. var nodeForKind = new MemoryNodeBuilder[((int)GCRootKind.Max) + 1]; var otherRoots = root.FindOrCreateChild("[other roots]"); nodeForKind[(int)GCRootKind.LocalVar] = otherRoots.FindOrCreateChild("[unknown local vars]"); for (int i = 0; i < nodeForKind.Length; i++) { if (nodeForKind[i] == null) { nodeForKind[i] = otherRoots.FindOrCreateChild("[" + ((GCRootKind)i).ToString().ToLower() + " handles]"); } } var unreachable = root.FindOrCreateChild("[unreachable but not yet collected]"); foreach (var gcRoot in heap.Roots) { if (gcRoot.HeapReference == 0) { continue; } // TODO FIX NOW condition should not be needed, should be able to assert it. if (!heap.IsInHeap(gcRoot.HeapReference)) { continue; } Debug.Assert(heap.IsInHeap(gcRoot.HeapReference)); // Make a node for it, and notice if the root is already in the heap. var lastLimit = NodeIndexLimit; var newNodeIdx = GetNodeIndex(gcRoot.HeapReference); var nodeForGcRoot = nodeForKind[(int)gcRoot.Kind]; if (gcRoot.Type != null) { var prefix = "other"; if (gcRoot.Kind == GCRootKind.StaticVar) { prefix = "static"; } else if (gcRoot.Kind == GCRootKind.LocalVar) { prefix = "local"; } var appDomainNode = root.FindOrCreateChild("[appdomain " + gcRoot.AppDomainName + "]"); var varKindNode = appDomainNode.FindOrCreateChild("[" + prefix + " vars]"); var moduleName = Path.GetFileNameWithoutExtension(gcRoot.Type.ModuleFilePath); var moduleNode = varKindNode.FindOrCreateChild("[" + prefix + " vars " + moduleName + "]"); var typeNode = moduleNode.FindOrCreateChild("[" + prefix + " vars " + gcRoot.Type.Name + "]"); var name = gcRoot.Type.Name + "+" + gcRoot.Name + " [" + prefix + " var]"; nodeForGcRoot = typeNode.FindOrCreateChild(name, gcRoot.Type.ModuleFilePath); // TODO REMOVE // if (nodeForGcRoot.Size == 0) // nodeForGcRoot.Size = 4; } // Give the user a bit more information about runtime internal handles if (gcRoot.Kind == GCRootKind.Pinning) { var pinnedType = heap.GetObjectType(gcRoot.HeapReference); if (pinnedType.Name == "System.Object []") { // Traverse pinned object arrays a bit 'manually' here so we tell the users they are likely handles. var handleArray = new MemoryNodeBuilder( this, "[likely runtime object array handle table]", pinnedType.ModuleFilePath, newNodeIdx); handleArray.Size = pinnedType.GetSize(gcRoot.HeapReference); nodeForGcRoot.AddChild(handleArray); pinnedType.EnumerateRefsOfObjectCarefully(gcRoot.HeapReference, delegate(Address childRef, int childFieldOffset) { // Poor man's visited bit. If we did not need to add a node, then we have visited it. var childLastLimit = NodeIndexLimit; var childNodeIdx = GetNodeIndex(childRef); if (childNodeIdx < childLastLimit) // Already visited, simply add the child and move one { handleArray.AddChild(childNodeIdx); return; } var childType = heap.GetObjectType(childRef); if (childType.Name == "System.String") { // TODO FIX NOW: Only want to morph the name if something else does not point at it. var literal = new MemoryNodeBuilder( this, "[likely string literal]", childType.ModuleFilePath, childNodeIdx); literal.Size = childType.GetSize(childRef); handleArray.AddChild(literal); } else { handleArray.AddChild(childNodeIdx); toDo.Enqueue(childNodeIdx); } }); continue; // we are done processing this node. } } nodeForGcRoot.AddChild(newNodeIdx); if (newNodeIdx >= lastLimit) // have not been visited. { toDo.Enqueue(newNodeIdx); } } root.AllocateTypeIndexes(); // Create the necessary types. int memoryTypesStart = (int)NodeTypeIndexLimit; foreach (var type in heap.Types) { NodeTypeIndex nodeType = CreateType(type.Name, type.ModuleFilePath); Debug.Assert((int)nodeType == (int)type.Index + memoryTypesStart); } var children = new GrowableArray <NodeIndex>(); while (toDo.Count > 0) { var nodeIndex = toDo.Dequeue(); var objRef = GetAddress(nodeIndex); children.Clear(); GCHeapType objType = heap.GetObjectType(objRef); objType.EnumerateRefsOfObjectCarefully(objRef, delegate(Address childRef, int childFieldOffset) { Debug.Assert(heap.IsInHeap(childRef)); var lastLimit = NodeIndexLimit; var childNodeIdx = GetNodeIndex(childRef); children.Add(childNodeIdx); // Poor man's visited bit. If the index we just asked for is a new node, put it in the work queue if (childNodeIdx >= lastLimit) { toDo.Enqueue(childNodeIdx); } }); int objSize = objType.GetSize(objRef); Debug.Assert(objSize > 0); SetNode(nodeIndex, (NodeTypeIndex)((int)objType.Index + memoryTypesStart), objSize, children); } long unreachableSize = (heap.TotalSize - TotalSize); unreachable.Size = (int)unreachableSize; if (unreachable.Size != unreachableSize) { unreachable.Size = int.MaxValue; // TODO not correct on overflow } // We are done! RootIndex = root.Build(); AllowReading(); }
void Read(TextReader reader) { var stack = new GrowableArray <StackSourceCallStackIndex>(); var line = reader.ReadLine(); // Skip the first line, which is column headers. var sample = new StackSourceSample(this); for (; ;) { line = reader.ReadLine(); if (line == null) { break; } // 0 1 2 3 4 5 6 7 8 // Order, # of Calls, % Incl Time, % Excl Time, Depth, Function, Module, Incl Time, Excl Time,% Sw. Out, Incl Switched Out, Type, Comments Min Avg Max Excl Switched Out int idx = 0; int depth = 0; string method = null; string module = null; int intVal; long longVal; for (int col = 0; col <= 8; col++) { var newIdx = line.IndexOf('\t', idx); Debug.Assert(0 < newIdx); if (newIdx < 0) { goto SKIP; } switch (col) { case 1: int.TryParse(line.Substring(idx, newIdx - idx), System.Globalization.NumberStyles.Number, null, out intVal); sample.Count = intVal; break; case 4: int.TryParse(line.Substring(idx, newIdx - idx), System.Globalization.NumberStyles.Number, null, out depth); break; case 5: while (idx < newIdx) { if (line[idx] != ' ') { break; } idx++; } method = line.Substring(idx, newIdx - idx); method = method.Replace((char)0xFFFD, '@'); // They used this character to separate the method name from signature. break; case 6: module = ""; if (depth != 0) { module = line.Substring(idx, newIdx - idx); } break; case 8: long.TryParse(line.Substring(idx, newIdx - idx), System.Globalization.NumberStyles.Number, null, out longVal); sample.Metric = longVal / 1000000; // TODO what is the metric? break; } idx = newIdx + 1; } var moduleIdx = Interner.ModuleIntern(module); var frameIdx = Interner.FrameIntern(method, moduleIdx); var prevFrame = StackSourceCallStackIndex.Invalid; if (0 < depth && depth <= stack.Count) { prevFrame = stack[depth - 1]; } var callStackIdx = Interner.CallStackIntern(frameIdx, prevFrame); if (depth < stack.Count) { stack.Count = depth; } stack.Add(callStackIdx); sample.StackIndex = callStackIdx; AddSample(sample); SKIP :; } Interner.DoneInterning(); }