Ejemplo n.º 1
0
    internal unsafe void ConvertHeapDataToGraph()
    {
        if (m_converted)
        {
            return;
        }
        m_converted = true;

        if (!m_seenStart)
        {
            throw new ApplicationException("ETL file did not include a Start Heap Dump Event");
        }

        if (!m_ignoreEvents)
        {
            throw new ApplicationException("ETL file did not include a Stop Heap Dump Event");
        }

        // Since we may have multiple roots, I create a pseudo-node to act as its parent.
        var root = new MemoryNodeBuilder(m_graph, "[JS Roots]");

        var nodeNames = new GrowableArray <string>(1000);

        for (; ;)
        {
            BulkNodeValues node;
            if (!GetNextNode(out node))
            {
                break;
            }

            // Get the node index
            var nodeIdx = m_graph.GetNodeIndex(node.Address);
            m_children.Clear();

            // Get the basic type name
            var typeName = "?";
            if (0 <= node.TypeNameId)
            {
                typeName = m_stringTable[node.TypeNameId];
                if (typeName.Length > 6 && typeName.EndsWith("Object"))
                {
                    typeName = "JS" + typeName.Substring(0, typeName.Length - 6);
                }
            }
            var relationships = "";

            // Process the edges (which can add children)
            for (int i = 0; i < node.EdgeCount; i++)
            {
                BulkEdgeValues edge;
                if (!GetNextEdge(out edge))
                {
                    throw new ApplicationException("Missing Edge Nodes in ETW data");
                }

                // Is this an edge to another object?  (externals count)
                if (edge.TargetType == EdgeTargetType.Object || edge.TargetType == EdgeTargetType.External)
                {
                    var childIdx = m_graph.GetNodeIndex((Address)edge.Value);

                    // Get the property name if it has one
                    string childPropertyName = null;
                    if (edge.RelationshipType == EdgeRelationshipType.NamedProperty || edge.RelationshipType == EdgeRelationshipType.Event)
                    {
                        childPropertyName = m_stringTable[edge.NameId];
                    }
                    else if (edge.RelationshipType == EdgeRelationshipType.IndexedProperty)
                    {
                        // The edge is an element of an array and the NameID is the index in the array.
                        // We want to treat all index properties as a single field
                        childPropertyName = "[]";
                    }
                    else if (edge.RelationshipType == EdgeRelationshipType.InternalProperty)
                    {
                        childPropertyName = "InternalProperty";
                    }

                    if (childPropertyName != null)
                    {
                        // Remember the property so that when we display the target object, we show that too.
                        if ((int)childIdx >= nodeNames.Count)
                        {
                            nodeNames.Count = (int)childIdx + 100;   // expand by at least 100.
                        }
                        nodeNames[(int)childIdx] = childPropertyName;
                    }
                    m_children.Add(childIdx);
                }
                else if (edge.TargetType == EdgeTargetType.BSTR)
                {
                    if (edge.RelationshipType == EdgeRelationshipType.RelationShip)
                    {
                        // This is extra information (typically the tag or a class of a HTML DOM object).  Add it to the type name.
                        var relationshipName  = m_stringTable[edge.NameId];
                        var relationshipValue = m_stringTable[(int)edge.Value];
                        if (relationships.Length > 0)
                        {
                            relationships += " ";
                        }
                        relationships += relationshipName + ":" + relationshipValue;
                    }
                }
            }

            // Get the property name we saved from things that refer to this object.
            string propertyName = null;
            if ((int)nodeIdx < nodeNames.Count)
            {
                propertyName = nodeNames[(int)nodeIdx];
                if (propertyName != null)
                {
                    nodeNames[(int)nodeIdx] = null;
                }
            }

            // Process the attributes.  We can get a good function name as well as some more children from the attributes.
            int objSize = node.Size;
            for (int i = 0; i < node.AttributeCount; i++)
            {
                BulkAttributeValues attribute;
                if (!GetNextAttribute(out attribute))
                {
                    throw new ApplicationException("Missing Attribute Nodes in ETW data");
                }

                // TODO FIX NOW Currently I include the prototype link.  is this a good idea?
                if (attribute.Type == AttributeType.Prototype)
                {
                    m_children.Add(m_graph.GetNodeIndex(attribute.Value));
                }
                else if (attribute.Type == AttributeType.Scope)
                {
                    // TODO FIX NOW: it seems that Value is truncated to 32 bits and we have to restore it.
                    // Feels like a hack (and not clear if it is correct)
                    var target = attribute.Value;
                    if ((target >> 32) == 0 && (node.Address >> 32) != 0)
                    {
                        target += node.Address & 0xFFFFFFFF00000000;
                    }
                    m_children.Add(m_graph.GetNodeIndex(target));
                }
                // WPA does this, I don't really understand it.
                if (attribute.Type == AttributeType.TextChildrenSize)
                {
                    objSize += (int)attribute.Value;
                }
                else if (attribute.Type == AttributeType.FunctionName)
                {
                    propertyName = m_stringTable[(int)attribute.Value];
                }
            }


            if (relationships.Length > 0)
            {
                typeName += " <|" + relationships + "|>";
            }
            // Create the complete type name
            if ((node.Flags & ObjectFlags.WINRT) != 0)
            {
                typeName = "(WinRT " + " " + typeName + ")";
            }
            else
            {
                typeName = "(Type " + " " + typeName + ")";
            }
            if (propertyName != null)
            {
                typeName = propertyName + " " + typeName;
            }

            // typeName += " [0x" + node.Address.ToString("x") + "]";
            var typeIdx = GetTypeIndex(typeName, node.Size);

            if ((node.Flags & ObjectFlags.IS_ROOT) != 0)
            {
                root.AddChild(nodeIdx);
            }

            if (!m_graph.IsDefined(nodeIdx))
            {
                m_graph.SetNode(nodeIdx, typeIdx, objSize, m_children);
            }
            else
            {
                // Only external objects might be listed twice.
                Debug.Assert((node.Flags & (ObjectFlags.EXTERNAL | ObjectFlags.EXTERNAL_UNKNOWN | ObjectFlags.EXTERNAL_DISPATCH |
                                            ObjectFlags.WINRT_DELEGATE | ObjectFlags.WINRT_INSTANCE | ObjectFlags.WINRT_NAMESPACE |
                                            ObjectFlags.WINRT_RUNTIMECLASS)) != 0);
            }
        }

        root.Build();
        m_graph.RootIndex = root.Index;
    }
    /// <summary>
    /// Sets up the callbacks needed to do a heap dump (work need before processing the events()
    /// </summary>
    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.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.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;

            default:
                throw new Exception("Invalid generation in GCGenerationRangeTraceData");
            }
            m_dotNetHeapInfo.Segments.Add(segment);
        };
    }