예제 #1
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);
        }
예제 #2
0
        private async Task HandleGCEvents(EventPipeEventSource source, int pid, Func <Task> stopFunc, CancellationToken token)
        {
            int gcNum = -1;

            Action <GCStartTraceData, Action> gcStartHandler = (GCStartTraceData data, Action taskComplete) =>
            {
                if (data.ProcessID != pid)
                {
                    return;
                }

                taskComplete();

                if (gcNum < 0 && data.Depth == 2 && data.Type != GCType.BackgroundGC)
                {
                    gcNum = data.Count;
                }
            };

            Action <GCBulkNodeTraceData, Action> gcBulkNodeHandler = (GCBulkNodeTraceData data, Action taskComplete) =>
            {
                if (data.ProcessID != pid)
                {
                    return;
                }

                taskComplete();
            };

            Action <GCEndTraceData, Action> gcEndHandler = (GCEndTraceData data, Action taskComplete) =>
            {
                if (data.ProcessID != pid)
                {
                    return;
                }

                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 => source.Clr.GCStart += handler,
                      handler => source.Clr.GCStart -= handler,
                      token);
            using var gcBulkNodeTaskSource = new EventTaskSource <Action <GCBulkNodeTraceData> >(
                      taskComplete => data => gcBulkNodeHandler(data, taskComplete),
                      handler => source.Clr.GCBulkNode += handler,
                      handler => source.Clr.GCBulkNode -= handler,
                      token);
            using var gcStopTaskSource = new EventTaskSource <Action <GCEndTraceData> >(
                      taskComplete => data => gcEndHandler(data, taskComplete),
                      handler => source.Clr.GCStop += handler,
                      handler => source.Clr.GCStop -= handler,
                      token);
            using var sourceCompletedTaskSource = new EventTaskSource <Action>(
                      taskComplete => taskComplete,
                      handler => source.Completed += handler,
                      handler => source.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, source, pid.ToString(CultureInfo.InvariantCulture));

            // 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 stopFunc();

            // 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();
        }
예제 #3
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);
    }