static void Main() { int expectedNumberOfNodes = 1000; MemoryGraph memoryGraph = new MemoryGraph(expectedNumberOfNodes); GrowableArray <NodeIndex> tempForChildren = new GrowableArray <NodeIndex>(); // We can make a new Node index NodeIndex newNodeIdx = memoryGraph.CreateNode(); NodeIndex childIdx = memoryGraph.CreateNode(); // NodeTypeIndex newNodeType = memoryGraph.CreateType("MyChild"); memoryGraph.SetNode(childIdx, newType, 100, tempForChildren); memoryGraph.AllowReading(); // Serialize to a file memoryGraph.WriteAsBinaryFile("file.gcHeap"); // Can unserialize easily. // var readBackIn = MemoryGraph.ReadFromBinaryFile("file.gcHeap"); }
public static MemoryGraph ReadMemoryGraphFromXml(XmlReader reader) { if (reader.NodeType != XmlNodeType.Element) { throw new InvalidOperationException("Must advance to MemoryGraph element (e.g. call ReadToDescendant)"); } var expectedSize = 1000; var nodeCount = reader.GetAttribute("NodeCount"); if (nodeCount != null) { expectedSize = int.Parse(nodeCount) + 1; // 1 for undefined } MemoryGraph graph = new MemoryGraph(10); Debug.Assert((int)graph.NodeTypeIndexLimit == 1); var firstNode = graph.CreateNode(); // Use one up Debug.Assert(firstNode == 0); Debug.Assert((int)graph.NodeIndexLimit == 1); var inputDepth = reader.Depth; reader.Read(); // Advance to children while (inputDepth < reader.Depth) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "NodeTypes": ReadNodeTypesFromXml(reader, graph); break; case "Nodes": ReadNodesFromXml(reader, graph); break; case "RootIndex": graph.RootIndex = (NodeIndex)reader.ReadElementContentAsInt(); break; default: Debug.WriteLine("Skipping unknown element {0}", reader.Name); reader.Skip(); break; } } else if (!reader.Read()) { break; } } graph.AllowReading(); return(graph); }
/// <summary> /// Read in the memory dump from javaScriptEtlName. Since there can be more than one, choose the first one /// after double startTimeRelativeMSec. If processId is non-zero only that process is considered, otherwise it considered /// the first heap dump regardless of process. /// </summary> public MemoryGraph Read(string javaScriptEtlName, int processId = 0, double startTimeRelativeMSec = 0) { var ret = new MemoryGraph(10000); Append(ret, javaScriptEtlName, processId, startTimeRelativeMSec); ret.AllowReading(); return(ret); }
public MemoryGraph Read(TraceEventDispatcher source, string processNameOrId = null, double startTimeRelativeMSec = 0) { var ret = new MemoryGraph(10000); Append(ret, source, processNameOrId, startTimeRelativeMSec); ret.AllowReading(); return(ret); }
/// <summary> /// Read in the memory dump from javaScriptEtlName. Since there can be more than one, choose the first one /// after double startTimeRelativeMSec. If processId is non-zero only that process is considered, otherwise it considered /// the first heap dump regardless of process. /// </summary> public MemoryGraph Read(string etlFilePath, string processNameOrId = null, double startTimeRelativeMSec = 0) { m_etlFilePath = etlFilePath; var ret = new MemoryGraph(10000); Append(ret, etlFilePath, processNameOrId, startTimeRelativeMSec); ret.AllowReading(); return(ret); }
public static MemoryGraph Create(string dllPath, SymbolReader symbolReader) { var ret = new MemoryGraph(1000); string pdbPath = symbolReader.FindSymbolFilePathForModule(dllPath); symbolReader.Log.WriteLine("Got PDB path {0}", pdbPath); NativeSymbolModule module = symbolReader.OpenNativeSymbolFile(pdbPath); List <Symbol> symbols = new List <Symbol>(); AddAllChildren(symbols, module.GlobalSymbol); symbols.Sort(); /****** Make a graph out of the symbols ******/ // Put all nodes under this root. var rootChildren = new GrowableArray <NodeIndex>(1000); // Create a node for each symbol uint lastRVA = 0; string lastName = "Header"; var empty = new GrowableArray <NodeIndex>(); foreach (var symbol in symbols) { var symRVA = symbol.RVA; int lastSize = (int)symRVA - (int)lastRVA; NodeTypeIndex typeIdx = ret.CreateType(lastName, null, lastSize); NodeIndex nodeIdx = ret.CreateNode(); ret.SetNode(nodeIdx, typeIdx, lastSize, empty); rootChildren.Add(nodeIdx); lastName = symbol.Name; lastRVA = symRVA; } // TODO FIX NOW dropping the last symbol. // Create the root node. NodeIndex rootIdx = ret.CreateNode(); NodeTypeIndex rootTypeIdx = ret.CreateType("METHODS"); ret.SetNode(rootIdx, rootTypeIdx, 0, rootChildren); ret.RootIndex = rootIdx; ret.AllowReading(); return(ret); }
internal static bool TryCollectMemoryGraph(CancellationToken ct, int processId, int timeout, bool verbose, out MemoryGraph memoryGraph) { var heapInfo = new DotNetHeapInfo(); var log = verbose ? Console.Out : TextWriter.Null; memoryGraph = new MemoryGraph(50_000); if (!EventPipeDotNetHeapDumper.DumpFromEventPipe(ct, processId, memoryGraph, log, timeout, heapInfo)) { return(false); } memoryGraph.AllowReading(); return(true); }
public MemoryGraph Read(string projectNMetaDataLog) { m_graph = new MemoryGraph(1000); m_knownTypes = new Dictionary <string, NodeTypeIndex>(1000); using (TextReader reader = File.OpenText(projectNMetaDataLog)) { int lineNum = 0; string line; try { // Skip headers line line = reader.ReadLine(); lineNum++; if (line == null) { return(null); } LineData lineData = new LineData(); for (; ;) { line = reader.ReadLine(); if (line == null) { break; } lineNum++; Match m = Regex.Match(line, "^(\\S+), +(\\S+), +\"(.*)\", +\"(.*?)\"$"); if (m.Success) { uint newOffset = uint.Parse(m.Groups[1].Value, NumberStyles.HexNumber) & 0xFFFFFF; lineData.Size = (int)(newOffset - lineData.Offset); if (lineNum > 2) { NodeIndex nodeIndex = AddLineData(ref lineData); if (lineNum == 3) { m_graph.RootIndex = nodeIndex; } } lineData.Offset = newOffset; lineData.Kind = m.Groups[2].Value.Replace("\\\"", "\"").Replace("\\\\", "\\"); lineData.Name = m.Groups[3].Value.Replace("\\\"", "\"").Replace("\\\\", "\\"); lineData.Children.Clear(); if (m.Groups[4].Length > 0) { string[] handleStrs = m.Groups[4].Value.Split(' '); foreach (var handleStr in handleStrs) { lineData.Children.Add(m_graph.GetNodeIndex(uint.Parse(handleStr, NumberStyles.HexNumber) & 0xFFFFFF)); } } } else { throw new FileFormatException(); } } if (lineNum > 1) { lineData.Size = 16; // TODO Better estimate. AddLineData(ref lineData); } } catch (Exception e) { throw new FileFormatException("Error on line number " + lineNum + " " + e.Message); } } m_graph.AllowReading(); return(m_graph); }
/// <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); }
protected override async Task OnEventSourceAvailable(EventPipeEventSource eventSource, Func <Task> stopSessionAsync, CancellationToken token) { int gcNum = -1; Action <GCStartTraceData, Action> gcStartHandler = (GCStartTraceData data, Action taskComplete) => { taskComplete(); if (gcNum < 0 && data.Depth == 2 && data.Type != GCType.BackgroundGC) { gcNum = data.Count; } }; Action <GCBulkNodeTraceData, Action> gcBulkNodeHandler = (GCBulkNodeTraceData data, Action taskComplete) => { taskComplete(); }; Action <GCEndTraceData, Action> gcEndHandler = (GCEndTraceData data, Action taskComplete) => { if (data.Count == gcNum) { taskComplete(); } }; // Register event handlers on the event source and represent their completion as tasks using var gcStartTaskSource = new EventTaskSource <Action <GCStartTraceData> >( taskComplete => data => gcStartHandler(data, taskComplete), handler => eventSource.Clr.GCStart += handler, handler => eventSource.Clr.GCStart -= handler, token); using var gcBulkNodeTaskSource = new EventTaskSource <Action <GCBulkNodeTraceData> >( taskComplete => data => gcBulkNodeHandler(data, taskComplete), handler => eventSource.Clr.GCBulkNode += handler, handler => eventSource.Clr.GCBulkNode -= handler, token); using var gcStopTaskSource = new EventTaskSource <Action <GCEndTraceData> >( taskComplete => data => gcEndHandler(data, taskComplete), handler => eventSource.Clr.GCStop += handler, handler => eventSource.Clr.GCStop -= handler, token); using var sourceCompletedTaskSource = new EventTaskSource <Action>( taskComplete => taskComplete, handler => eventSource.Completed += handler, handler => eventSource.Completed -= handler, token); // A task that is completed whenever GC data is received Task gcDataTask = Task.WhenAny(gcStartTaskSource.Task, gcBulkNodeTaskSource.Task); Task gcStopTask = gcStopTaskSource.Task; DotNetHeapDumpGraphReader dumper = new DotNetHeapDumpGraphReader(TextWriter.Null) { DotNetHeapInfo = new DotNetHeapInfo() }; dumper.SetupCallbacks(_gcGraph, eventSource); // The event source will not always provide the GC events when it starts listening. However, // they will be provided when the event source is told to stop processing events. Give the // event source some time to produce the events, but if it doesn't start producing them within // a short amount of time (5 seconds), then stop processing events to allow them to be flushed. Task eventsTimeoutTask = Task.Delay(TimeSpan.FromSeconds(5), token); Task completedTask = await Task.WhenAny(gcDataTask, eventsTimeoutTask); token.ThrowIfCancellationRequested(); // If started receiving GC events, wait for the GC Stop event. if (completedTask == gcDataTask) { await gcStopTask; } // Stop receiving events; if haven't received events yet, this will force flushing of events. await stopSessionAsync(); // Wait for all received events to be processed. await sourceCompletedTaskSource.Task; // Check that GC data and stop events were received. This is done by checking that the // associated tasks have ran to completion. If one of them has not reached the completion state, then // fail the GC dump operation. if (gcDataTask.Status != TaskStatus.RanToCompletion || gcStopTask.Status != TaskStatus.RanToCompletion) { throw new InvalidOperationException("Unable to create GC dump due to incomplete GC data."); } dumper.ConvertHeapDataToGraph(); _gcGraph.AllowReading(); }