/// <summary> /// Deals with legacy formats. We should be able to get rid of eventually. /// </summary> private void PreVersion8FromStream(Deserializer deserializer) { DotNetHeapInfo = new DotNetHeapInfo(); deserializer.Read(out m_graph); DotNetHeapInfo.SizeOfAllSegments = deserializer.ReadInt64(); deserializer.ReadInt64(); // Size of dumped objects deserializer.ReadInt64(); // Number of dumped objects deserializer.ReadBool(); // All objects dumped if (deserializer.VersionBeingRead >= 5) { CollectionLog = deserializer.ReadString(); TimeCollected = new DateTime(deserializer.ReadInt64()); MachineName = deserializer.ReadString(); ProcessName = deserializer.ReadString(); ProcessID = deserializer.ReadInt(); TotalProcessCommit = deserializer.ReadInt64(); TotalProcessWorkingSet = deserializer.ReadInt64(); if (deserializer.VersionBeingRead >= 6) { // Skip the segments var count = deserializer.ReadInt(); for (int i = 0; i < count; i++) { deserializer.ReadObject(); } if (deserializer.VersionBeingRead >= 7) { deserializer.ReadBool(); // Is64bit } } } }
/// <summary> /// Collects a gcdump from a currently running process. /// </summary> /// <param name="ct">The cancellation token</param> /// <param name="console"></param> /// <param name="processId">The process to collect the gcdump from.</param> /// <param name="output">The output path for the collected gcdump.</param> /// <returns></returns> private static async Task <int> Collect(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose) { try { output = string.IsNullOrEmpty(output) ? $"{DateTime.Now.ToString(@"yyyyMMdd\_hhmmss")}_{processId}.gcdump" : output; FileInfo outputFileInfo = new FileInfo(output); if (outputFileInfo.Exists) { outputFileInfo.Delete(); } if (string.IsNullOrEmpty(outputFileInfo.Extension) || outputFileInfo.Extension != ".gcdump") { outputFileInfo = new FileInfo(outputFileInfo.FullName + ".gcdump"); } Console.Out.WriteLine($"Writing gcdump to '{outputFileInfo.FullName}'..."); var dumpTask = Task.Run(() => { var memoryGraph = new Graphs.MemoryGraph(50_000); var heapInfo = new DotNetHeapInfo(); if (!EventPipeDotNetHeapDumper.DumpFromEventPipe(ct, processId, memoryGraph, verbose ? Console.Out : TextWriter.Null, timeout, heapInfo)) { return(false); } memoryGraph.AllowReading(); GCHeapDump.WriteMemoryGraph(memoryGraph, outputFileInfo.FullName, "dotnet-gcdump"); return(true); }); var fDumpSuccess = await dumpTask; if (fDumpSuccess) { outputFileInfo.Refresh(); Console.Out.WriteLine($"\tFinished writing {outputFileInfo.Length} bytes."); return(0); } else if (ct.IsCancellationRequested) { Console.Out.WriteLine($"\tCancelled."); return(-1); } else { Console.Out.WriteLine($"\tFailed to collect gcdump. Try running with '-v' for more information."); return(-1); } } catch (Exception ex) { Console.Error.WriteLine($"[ERROR] {ex.ToString()}"); return(-1); } }
private GenerationAwareMemoryGraphBuilder( TextWriter log, int generationToCondemn, MemoryGraph memGraph, DotNetHeapInfo heapInfo) { _Log = log; _GenerationToCondemn = generationToCondemn; _OriginalMemoryGraph = memGraph; _OriginalStackSource = new MemoryGraphStackSource(memGraph, log); _HeapInfo = heapInfo; _OldNodeToNewParentMap = new MemoryNodeBuilder[(int)_OriginalMemoryGraph.NodeIndexLimit]; _OldNodeTypeStorage = _OriginalMemoryGraph.AllocTypeNodeStorage(); }
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); }
internal void ProcessHeapSnapshot() { // Constants. const string PinnedHandlesNodeName = "[Pinned handle]"; const string AsyncPinnedHandlesNodeName = "[AsyncPinned handle]"; const string OverlappedDataTypeName = "System.Threading.OverlappedData"; const string ByteArrayTypeName = "System.Byte[]"; const string ObjectArrayTypeName = "System.Object[]"; // Open the heap dump. GCHeapDump dump = new GCHeapDump(_HeapSnapshotFilePath); _ProcessID = dump.ProcessID; // Get the heap info. DotNetHeapInfo heapInfo = dump.DotNetHeapInfo; // Get the memory graph. MemoryGraph memoryGraph = dump.MemoryGraph; // Get the root node. NodeIndex rootIndex = memoryGraph.RootIndex; Node rootNode = memoryGraph.GetNode(rootIndex, memoryGraph.AllocNodeStorage()); // Allocate additional nodes and node types. Node handleClassNodeStorage = memoryGraph.AllocNodeStorage(); NodeType handleClassNodeTypeStorage = memoryGraph.AllocTypeNodeStorage(); Node pinnedObjectNodeStorage = memoryGraph.AllocNodeStorage(); NodeType pinnedObjectNodeTypeStorage = memoryGraph.AllocTypeNodeStorage(); Node pinnedObjectChildNodeStorage = memoryGraph.AllocNodeStorage(); NodeType pinnedObjectChildNodeTypeStorage = memoryGraph.AllocTypeNodeStorage(); Node userObjectNodeStorage = memoryGraph.AllocNodeStorage(); NodeType userObjectNodeTypeStorage = memoryGraph.AllocTypeNodeStorage(); Node arrayBufferNodeStorage = memoryGraph.AllocNodeStorage(); NodeType arrayBufferNodeTypeStorage = memoryGraph.AllocTypeNodeStorage(); // Create a dictionary of pinned roots by pinned object address. Dictionary <Address, PinningRoot> pinnedRoots = new Dictionary <Address, PinningRoot>(); // Iterate over the nodes that represent handle type (e.g. [AsyncPinned Handle], [Pinned Handle], etc.) for (NodeIndex handleClassNodeIndex = rootNode.GetFirstChildIndex(); handleClassNodeIndex != NodeIndex.Invalid; handleClassNodeIndex = rootNode.GetNextChildIndex()) { // Get the node. Node handleClassNode = memoryGraph.GetNode(handleClassNodeIndex, handleClassNodeStorage); NodeType handleClassNodeType = handleClassNode.GetType(handleClassNodeTypeStorage); // Iterate over all pinned handles. if (PinnedHandlesNodeName.Equals(handleClassNodeType.Name)) { for (NodeIndex pinnedObjectNodeIndex = handleClassNode.GetFirstChildIndex(); pinnedObjectNodeIndex != NodeIndex.Invalid; pinnedObjectNodeIndex = handleClassNode.GetNextChildIndex()) { Node pinnedObjectNode = memoryGraph.GetNode(pinnedObjectNodeIndex, pinnedObjectNodeStorage); NodeType pinnedObjectNodeType = pinnedObjectNode.GetType(pinnedObjectNodeTypeStorage); // Create an object to represent the pinned objects. PinningRoot pinnedRoot = new PinningRoot(GCHandleKind.Pinned); List <Address> objectAddresses = new List <Address>(); // Get the address of the OverlappedData and add it to the list of pinned objects. Address pinnedObjectAddress = memoryGraph.GetAddress(pinnedObjectNodeIndex); UInt16 pinnedObjectGeneration = (UInt16)heapInfo.GenerationFor(pinnedObjectAddress); pinnedRoot.PinnedObjects = new PinnedObject[] { new PinnedObject(pinnedObjectAddress, pinnedObjectNodeType.Name, (uint)pinnedObjectNode.Size, pinnedObjectGeneration) }; pinnedRoots.Add(pinnedObjectAddress, pinnedRoot); } } // Iterate over asyncpinned handles. if (AsyncPinnedHandlesNodeName.Equals(handleClassNodeType.Name)) { for (NodeIndex pinnedObjectNodeIndex = handleClassNode.GetFirstChildIndex(); pinnedObjectNodeIndex != NodeIndex.Invalid; pinnedObjectNodeIndex = handleClassNode.GetNextChildIndex()) { Node pinnedObjectNode = memoryGraph.GetNode(pinnedObjectNodeIndex, pinnedObjectNodeStorage); NodeType pinnedObjectNodeType = pinnedObjectNode.GetType(pinnedObjectNodeTypeStorage); // Iterate over all OverlappedData objects. if (OverlappedDataTypeName.Equals(pinnedObjectNodeType.Name)) { // Create an object to represent the pinned objects. PinningRoot pinnedRoot = new PinningRoot(GCHandleKind.AsyncPinned); List <Address> objectAddresses = new List <Address>(); List <PinnedObject> pinnedObjects = new List <PinnedObject>(); // Get the address of the OverlappedData and add it to the list of pinned objects. Address pinnedObjectAddress = memoryGraph.GetAddress(pinnedObjectNodeIndex); UInt16 pinnedObjectGeneration = (UInt16)heapInfo.GenerationFor(pinnedObjectAddress); objectAddresses.Add(pinnedObjectAddress); pinnedObjects.Add(new PinnedObject(pinnedObjectAddress, pinnedObjectNodeType.Name, (uint)pinnedObjectNode.Size, pinnedObjectGeneration)); // Get the buffer or list of buffers that are pinned by the asyncpinned handle. for (NodeIndex userObjectNodeIndex = pinnedObjectNode.GetFirstChildIndex(); userObjectNodeIndex != NodeIndex.Invalid; userObjectNodeIndex = pinnedObjectNode.GetNextChildIndex()) { Node userObjectNode = memoryGraph.GetNode(userObjectNodeIndex, userObjectNodeStorage); NodeType userObjectNodeType = userObjectNode.GetType(userObjectNodeTypeStorage); if (userObjectNodeType.Name.StartsWith(ByteArrayTypeName)) { // Get the address. Address bufferAddress = memoryGraph.GetAddress(userObjectNodeIndex); UInt16 bufferGeneration = (UInt16)heapInfo.GenerationFor(bufferAddress); objectAddresses.Add(bufferAddress); pinnedObjects.Add(new PinnedObject(bufferAddress, userObjectNodeType.Name, (uint)userObjectNode.Size, bufferGeneration)); } else if (userObjectNodeType.Name.StartsWith(ObjectArrayTypeName)) { for (NodeIndex arrayBufferNodeIndex = userObjectNode.GetFirstChildIndex(); arrayBufferNodeIndex != NodeIndex.Invalid; arrayBufferNodeIndex = userObjectNode.GetNextChildIndex()) { Node arrayBufferNode = memoryGraph.GetNode(arrayBufferNodeIndex, arrayBufferNodeStorage); NodeType arrayBufferNodeType = arrayBufferNode.GetType(arrayBufferNodeTypeStorage); if (arrayBufferNodeType.Name.StartsWith(ByteArrayTypeName)) { // Get the address. Address bufferAddress = memoryGraph.GetAddress(arrayBufferNodeIndex); UInt16 bufferGeneration = (UInt16)heapInfo.GenerationFor(bufferAddress); objectAddresses.Add(bufferAddress); pinnedObjects.Add(new PinnedObject(bufferAddress, arrayBufferNodeType.Name, (uint)arrayBufferNode.Size, bufferGeneration)); } } } } // Assign the list of objects into the pinned root. pinnedRoot.PinnedObjects = pinnedObjects.ToArray(); foreach (Address objectAddress in objectAddresses) { // TODO: Handle objects that are pinned multiple times (?) pinnedRoots.Add(objectAddress, pinnedRoot); } } } } } _RootTable = pinnedRoots; }
/// <summary> /// Given a factory for creating an EventPipe session with the appropriate provider and keywords turned on, /// generate a GCHeapDump using the resulting events. The correct keywords and provider name /// are given as input to the Func eventPipeEventSourceFactory. /// </summary> /// <param name="processID"></param> /// <param name="eventPipeEventSourceFactory">A delegate for creating and stopping EventPipe sessions</param> /// <param name="memoryGraph"></param> /// <param name="log"></param> /// <param name="dotNetInfo"></param> /// <returns></returns> public static bool DumpFromEventPipe(CancellationToken ct, int processID, MemoryGraph memoryGraph, TextWriter log, int timeout, DotNetHeapInfo dotNetInfo = null) { var startTicks = Stopwatch.GetTimestamp(); Func <TimeSpan> getElapsed = () => TimeSpan.FromTicks(Stopwatch.GetTimestamp() - startTicks); var dumper = new DotNetHeapDumpGraphReader(log) { DotNetHeapInfo = dotNetInfo }; try { TimeSpan lastEventPipeUpdate = getElapsed(); bool fDone = false; log.WriteLine("{0,5:n1}s: Creating type table flushing task", getElapsed().TotalSeconds); using (EventPipeSessionController typeFlushSession = new EventPipeSessionController(processID, new List <EventPipeProvider> { new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) }, false)) { log.WriteLine("{0,5:n1}s: Flushing the type table", getElapsed().TotalSeconds); typeFlushSession.Source.AllEvents += (traceEvent) => { if (!fDone) { fDone = true; Task.Run(() => typeFlushSession.EndSession()) .ContinueWith(_ => typeFlushSession.Source.StopProcessing()); } }; typeFlushSession.Source.Process(); log.WriteLine("{0,5:n1}s: Done flushing the type table", getElapsed().TotalSeconds); } // Start the providers and trigger the GCs. log.WriteLine("{0,5:n1}s: Requesting a .NET Heap Dump", getElapsed().TotalSeconds); using EventPipeSessionController gcDumpSession = new EventPipeSessionController(processID, new List <EventPipeProvider> { new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Verbose, (long)(ClrTraceEventParser.Keywords.GCHeapSnapshot)) }); log.WriteLine("{0,5:n1}s: gcdump EventPipe Session started", getElapsed().TotalSeconds); int gcNum = -1; gcDumpSession.Source.Clr.GCStart += delegate(GCStartTraceData data) { if (data.ProcessID != processID) { return; } eventPipeDataPresent = true; if (gcNum < 0 && data.Depth == 2 && data.Type != GCType.BackgroundGC) { gcNum = data.Count; log.WriteLine("{0,5:n1}s: .NET Dump Started...", getElapsed().TotalSeconds); } }; gcDumpSession.Source.Clr.GCStop += delegate(GCEndTraceData data) { if (data.ProcessID != processID) { return; } if (data.Count == gcNum) { log.WriteLine("{0,5:n1}s: .NET GC Complete.", getElapsed().TotalSeconds); dumpComplete = true; } }; gcDumpSession.Source.Clr.GCBulkNode += delegate(GCBulkNodeTraceData data) { if (data.ProcessID != processID) { return; } eventPipeDataPresent = true; if ((getElapsed() - lastEventPipeUpdate).TotalMilliseconds > 500) { log.WriteLine("{0,5:n1}s: Making GC Heap Progress...", getElapsed().TotalSeconds); } lastEventPipeUpdate = getElapsed(); }; if (memoryGraph != null) { dumper.SetupCallbacks(memoryGraph, gcDumpSession.Source, processID.ToString()); } // Set up a separate thread that will listen for EventPipe events coming back telling us we succeeded. Task readerTask = Task.Run(() => { // cancelled before we began if (ct.IsCancellationRequested) { return; } log.WriteLine("{0,5:n1}s: Starting to process events", getElapsed().TotalSeconds); gcDumpSession.Source.Process(); log.WriteLine("{0,5:n1}s: EventPipe Listener dying", getElapsed().TotalSeconds); }, ct); for (; ;) { if (ct.IsCancellationRequested) { log.WriteLine("{0,5:n1}s: Cancelling...", getElapsed().TotalSeconds); break; } if (readerTask.Wait(100)) { break; } if (!eventPipeDataPresent && getElapsed().TotalSeconds > 5) // Assume it started within 5 seconds. { log.WriteLine("{0,5:n1}s: Assume no .NET Heap", getElapsed().TotalSeconds); break; } if (getElapsed().TotalSeconds > timeout) // Time out after `timeout` seconds. defaults to 30s. { log.WriteLine("{0,5:n1}s: Timed out after {1} seconds", getElapsed().TotalSeconds, timeout); break; } if (dumpComplete) { break; } } var stopTask = Task.Run(() => { log.WriteLine("{0,5:n1}s: Shutting down gcdump EventPipe session", getElapsed().TotalSeconds); gcDumpSession.EndSession(); log.WriteLine("{0,5:n1}s: gcdump EventPipe session shut down", getElapsed().TotalSeconds); }, ct); try { while (!Task.WaitAll(new Task[] { readerTask, stopTask }, 1000)) { log.WriteLine("{0,5:n1}s: still reading...", getElapsed().TotalSeconds); } } catch (AggregateException ae) // no need to throw if we're just cancelling the tasks { foreach (var e in ae.Flatten().InnerExceptions) { if (!(e is TaskCanceledException)) { throw ae; } } } log.WriteLine("{0,5:n1}s: gcdump EventPipe Session closed", getElapsed().TotalSeconds); if (ct.IsCancellationRequested) { return(false); } if (eventPipeDataPresent) { dumper.ConvertHeapDataToGraph(); // Finish the conversion. } } catch (Exception e) { log.WriteLine($"{getElapsed().TotalSeconds,5:n1}s: [Error] Exception during gcdump: {e.ToString()}"); } log.WriteLine("[{0,5:n1}s: Done Dumping .NET heap success={1}]", getElapsed().TotalSeconds, dumpComplete); return(dumpComplete); }
/// <summary> /// Add the nodes of the .NET heap for the process 'processID' to 'memoryGraph' sending diagnostic /// messages to 'log'. If 'memoryGraph' is null, the ETW providers are triggered by we obviously /// don't update the memoryGraph. Thus it is only done for the side effect of triggering a .NET heap /// dump. returns true if successful. /// </summary> static public bool Dump(int processID, MemoryGraph memoryGraph, TextWriter log, DotNetHeapInfo dotNetInfo = null) { var sw = Stopwatch.StartNew(); var dumper = new DotNetHeapDumpGraphReader(log); dumper.DotNetHeapInfo = dotNetInfo; bool dumpComplete = false; bool listening = false; TraceEventSession session = null; Task readerTask = null; try { bool etwDataPresent = false; TimeSpan lastEtwUpdate = sw.Elapsed; // Set up a separate thread that will listen for ETW events coming back telling us we succeeded. readerTask = Task.Factory.StartNew(delegate { string sessionName = "PerfViewGCHeapSession"; session = new TraceEventSession(sessionName, null); int gcNum = -1; session.BufferSizeMB = 256; // Events come pretty fast, so make the buffer bigger. // Start the providers and trigger the GCs. log.WriteLine("{0,5:n1}s: Requesting a .NET Heap Dump", sw.Elapsed.TotalSeconds); // Have to turn on Kernel provider first (before touching Source) so we do it here. session.EnableKernelProvider(KernelTraceEventParser.Keywords.Process | KernelTraceEventParser.Keywords.ImageLoad); session.Source.Clr.GCStart += delegate(GCStartTraceData data) { if (data.ProcessID != processID) { return; } etwDataPresent = true; if (gcNum < 0 && data.Depth == 2 && data.Type != GCType.BackgroundGC) { gcNum = data.Count; log.WriteLine("{0,5:n1}s: Dot Net Dump Started...", sw.Elapsed.TotalSeconds); } }; session.Source.Clr.GCStop += delegate(GCEndTraceData data) { if (data.ProcessID != processID) { return; } if (data.Count == gcNum) { log.WriteLine("{0,5:n1}s: DotNet GC Complete.", sw.Elapsed.TotalSeconds); dumpComplete = true; } }; session.Source.Clr.GCBulkNode += delegate(GCBulkNodeTraceData data) { if (data.ProcessID != processID) { return; } etwDataPresent = true; if ((sw.Elapsed - lastEtwUpdate).TotalMilliseconds > 500) { log.WriteLine("{0,5:n1}s: Making GC Heap Progress...", sw.Elapsed.TotalSeconds); } lastEtwUpdate = sw.Elapsed; }; if (memoryGraph != null) { dumper.SetupCallbacks(memoryGraph, session.Source, processID.ToString()); } listening = true; session.Source.Process(); log.WriteLine("{0,5:n1}s: ETW Listener dieing", sw.Elapsed.TotalSeconds); }); // Wait for thread above to start listening (should be very fast) while (!listening) { readerTask.Wait(1); } Debug.Assert(session != null); // Request the heap dump. We try to isolate this to a single process. var options = new TraceEventProviderOptions() { ProcessIDFilter = new List <int>() { processID } }; // There is a bug in the runtime 4.6.2 and earlier where we only clear the table of types we have already emitted when you ENABLE // the Clr Provider WITHOUT the ClrTraceEventParser.Keywords.Type keyword. we achive this by turning on just the GC events, // (which clears the Type table) and then turn all the events we need on. session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Informational, (ulong)ClrTraceEventParser.Keywords.GC, options); System.Threading.Thread.Sleep(50); // Wait for it to complete (it is async) // For non-project N we need module rundown to figure out the correct module name session.EnableProvider(ClrRundownTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)(ClrRundownTraceEventParser.Keywords.Loader | ClrRundownTraceEventParser.Keywords.ForceEndRundown), options); session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Informational, (ulong)ClrTraceEventParser.Keywords.GCHeapSnapshot, options); // Project N support. session.EnableProvider(ClrTraceEventParser.NativeProviderGuid, TraceEventLevel.Informational, (ulong)ClrTraceEventParser.Keywords.GCHeapSnapshot, options); for (;;) { if (readerTask.Wait(100)) { break; } if (!etwDataPresent && sw.Elapsed.TotalSeconds > 5) // Assume it started within 5 seconds. { log.WriteLine("{0,5:n1}s: Assume no Dot Heap", sw.Elapsed.TotalSeconds); break; } if (sw.Elapsed.TotalSeconds > 100) // Time out after 100 seconds. { log.WriteLine("{0,5:n1}s: Timed out after 100 seconds", sw.Elapsed.TotalSeconds); break; } // TODO FIX NOW, time out faster if we seek to be stuck if (dumpComplete) { break; } } if (etwDataPresent) { dumper.ConvertHeapDataToGraph(); // Finish the conversion. } } finally { // Stop the ETW providers log.WriteLine("{0,5:n1}s: Shutting down ETW session", sw.Elapsed.TotalSeconds); if (session != null) { session.Dispose(); } } if (readerTask != null) { log.WriteLine("{0,5:n1}s: Waiting for shutdown to complete.", sw.Elapsed.TotalSeconds); if (!readerTask.Wait(2000)) { log.WriteLine("{0,5:n1}s: Shutdown wait timed out after 2 seconds.", sw.Elapsed.TotalSeconds); } } log.WriteLine("[{0,5:n1}s: Done Dumping .NET heap success={1}]", sw.Elapsed.TotalSeconds, dumpComplete); return(dumpComplete); }
/// <summary> /// Dump the dot net Heap for process 'processID' to the etl file name 'etlFileName'. Send diagnostics to 'log'. /// If 'memoryGraph is non-null also update it to contain the heap Dump. If null you get just the ETL file. /// returns true if successful. /// </summary> static public bool DumpAsEtlFile(int processID, string etlFileName, TextWriter log, MemoryGraph memoryGraph = null, DotNetHeapInfo dotNetInfo = null) { bool success = false; log.WriteLine("Starting ETW logging on File {0}", etlFileName); using (var session = new TraceEventSession("PerfViewGCHeapETLSession", etlFileName)) { session.BufferSizeMB = 256; session.EnableKernelProvider(KernelTraceEventParser.Keywords.Process | KernelTraceEventParser.Keywords.Thread | KernelTraceEventParser.Keywords.ImageLoad); // Isolate this to a single process. var options = new TraceEventProviderOptions() { ProcessIDFilter = new List <int>() { processID } }; // There is a bug in the runtime 4.6.2 and earlier where we only clear the table of types we have already emitted when you ENABLE // the Clr Provider WITHOUT the ClrTraceEventParser.Keywords.Type keyword. We achieve this by turning on just the GC events, // (which clears the Type table) and then turn all the events we need on. // Note we do this here, as well as in Dump() because it only works if the CLR Type keyword is off (and we turn it on below) session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Informational, (ulong)ClrTraceEventParser.Keywords.GC, options); System.Threading.Thread.Sleep(50); // Wait for it to complete (it is async) // For non-project N we need module rundown to figure out the correct module name session.EnableProvider(ClrRundownTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)(ClrRundownTraceEventParser.Keywords.Loader | ClrRundownTraceEventParser.Keywords.ForceEndRundown), options); session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Informational, (ulong)(ClrTraceEventParser.Keywords.GCHeapDump | ClrTraceEventParser.Keywords.GC | ClrTraceEventParser.Keywords.Type | ClrTraceEventParser.Keywords.GCHeapAndTypeNames), options); // Project N support. session.EnableProvider(ClrTraceEventParser.NativeProviderGuid, TraceEventLevel.Informational, (ulong)(ClrTraceEventParser.Keywords.GCHeapDump | ClrTraceEventParser.Keywords.GC | ClrTraceEventParser.Keywords.Type | ClrTraceEventParser.Keywords.GCHeapAndTypeNames), options); success = Dump(processID, memoryGraph, log, dotNetInfo); log.WriteLine("Stopping ETW logging on {0}", etlFileName); } log.WriteLine("DumpAsETLFile returns. Success={0}", success); return(success); }
/// <summary> /// Dump the dot net Heap for process 'processID' to the etl file name 'etlFileName'. Send diagnostics to 'log'. /// If 'memoryGraph is non-null also update it to contain the heap Dump. If null you get just the ETL file. /// returns true if successful. /// </summary> static public bool DumpAsEtlFile(int processID, string etlFileName, TextWriter log, MemoryGraph memoryGraph = null, DotNetHeapInfo dotNetInfo = null) { bool success = false; log.WriteLine("Starting ETW logging on File {0}", etlFileName); using (var session = new TraceEventSession("PerfViewGCHeapETLSession", etlFileName)) { session.EnableKernelProvider(KernelTraceEventParser.Keywords.Process | KernelTraceEventParser.Keywords.Thread | KernelTraceEventParser.Keywords.ImageLoad); // Isolate this to a single process. var options = new TraceEventProviderOptions() { ProcessIDFilter = new List <int>() { processID } }; // For non-project N we need module rundown to figure out the correct module name session.EnableProvider(ClrRundownTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)(ClrRundownTraceEventParser.Keywords.Loader | ClrRundownTraceEventParser.Keywords.ForceEndRundown), options); session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Informational, (ulong)(ClrTraceEventParser.Keywords.GCHeapDump | ClrTraceEventParser.Keywords.GC | ClrTraceEventParser.Keywords.Type | ClrTraceEventParser.Keywords.Type | ClrTraceEventParser.Keywords.GCHeapAndTypeNames), options); // Project N support. session.EnableProvider(ClrTraceEventParser.NativeProviderGuid, TraceEventLevel.Informational, (ulong)(ClrTraceEventParser.Keywords.GCHeapDump | ClrTraceEventParser.Keywords.GC | ClrTraceEventParser.Keywords.Type | ClrTraceEventParser.Keywords.Type | ClrTraceEventParser.Keywords.GCHeapAndTypeNames), options); success = Dump(processID, memoryGraph, log, dotNetInfo); log.WriteLine("Stopping ETW logging on {0}", etlFileName); } log.WriteLine("DumpAsETLFile returns. Success={0}", success); return(success); }
/// <summary> /// Collects a gcdump from a currently running process. /// </summary> /// <param name="ct">The cancellation token</param> /// <param name="console"></param> /// <param name="processId">The process to collect the gcdump from.</param> /// <param name="output">The output path for the collected gcdump.</param> /// <returns></returns> public static async Task <int> Collect(CancellationToken ct, CommandLineApplication cmd, int processId, string output, int timeout, bool verbose) { try { if (processId < 0) { return(cmd.ExitWithError($"The PID cannot be negative: {processId}")); } if (processId == 0) { return(cmd.ExitWithError($"-p|--process-id is required")); } output = string.IsNullOrEmpty(output) ? $"{DateTime.Now.ToString(@"yyyyMMdd\_hhmmss")}_{processId}.gcdump" : output; FileInfo outputFileInfo = new FileInfo(output); if (outputFileInfo.Exists) { outputFileInfo.Delete(); } if (string.IsNullOrEmpty(outputFileInfo.Extension) || outputFileInfo.Extension != ".gcdump") { outputFileInfo = new FileInfo(outputFileInfo.FullName + ".gcdump"); } cmd.Out.WriteLine($"Writing gcdump to '{outputFileInfo.FullName}'..."); var dumpTask = Task.Run(() => { var memoryGraph = new Graphs.MemoryGraph(50_000); var heapInfo = new DotNetHeapInfo(); if (!EventPipeDotNetHeapDumper.DumpFromEventPipe(ct, processId, memoryGraph, verbose ? cmd.Out : TextWriter.Null, timeout, heapInfo)) { return(false); } memoryGraph.AllowReading(); GCHeapDump.WriteMemoryGraph(memoryGraph, outputFileInfo.FullName, "dotnet-gcdump"); return(true); }); var fDumpSuccess = await dumpTask; if (fDumpSuccess) { outputFileInfo.Refresh(); cmd.Out.WriteLine($"\tFinished writing {new Size(outputFileInfo.Length, SizeUnit.Bytes)}."); return(0); } else if (ct.IsCancellationRequested) { cmd.Out.WriteLine($"\tCancelled."); return(-1); } else { cmd.Out.WriteLine($"\tFailed to collect gcdump. Try running with '-v' for more information."); return(-1); } } catch (Exception ex) { return(cmd.ExitWithError($"[ERROR] {ex}")); } }