예제 #1
0
    /// <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
                }
            }
        }
    }
예제 #2
0
        /// <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();
 }
예제 #4
0
        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);
        }
예제 #5
0
        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;
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
    /// <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);
    }
예제 #8
0
    /// <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);
    }
예제 #9
0
    /// <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);
    }
예제 #10
0
        /// <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}"));
            }
        }