/// <summary>
    /// Add the nodes of the JS 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 JS heap
    /// dump. returns true if successful.
    /// </summary>
    static public bool Dump(int processID, MemoryGraph memoryGraph, TextWriter log)
    {
        var ver    = Environment.OSVersion.Version;
        var intVer = ver.Major * 10 + ver.Minor;

        if (intVer < 62)
        {
            log.WriteLine("JavaScript Heap Dumping only supported on Win8 or above.");
            return(false);
        }

        var  sw                   = Stopwatch.StartNew();
        var  dumper               = new JavaScriptDumpGraphReader(log);
        bool dumpComplete         = false;
        bool listening            = false;
        int  edgeRecords          = 0;
        TraceEventSession session = null;
        Task readerTask           = null;

        try
        {
            bool     jsDataPresent = false;
            TimeSpan lastJSUpdate  = 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 = "PerfViewJSHeapSession";
                session            = new TraceEventSession(sessionName, null);
                // Set up the JScript heap listener
                var etwJSParser = new JSDumpHeapTraceEventParser(session.Source);
                etwJSParser.JSDumpHeapEnvelopeStop += delegate(SummaryTraceData data)
                {
                    if (data.ProcessID == processID)
                    {
                        log.WriteLine("{0,5:n1}s: JavaScript GC Complete.", sw.Elapsed.TotalSeconds);
                        dumpComplete        = true;
                        memoryGraph.Is64Bit = (data.PointerSize == 8);
                    }
                };

                etwJSParser.JSDumpHeapEnvelopeStart += delegate(SettingsTraceData data)
                {
                    log.WriteLine("{0,5:n1}s: JS Heap Dump Started...", sw.Elapsed.TotalSeconds);
                    jsDataPresent = true;
                };

                etwJSParser.JSDumpHeapBulkEdge += delegate(BulkEdgeTraceData data)
                {
                    if (data.ProcessID == processID)
                    {
                        edgeRecords++;
                        if ((sw.Elapsed - lastJSUpdate).TotalMilliseconds > 500)
                        {
                            log.WriteLine("{0,5:n1}s: Making JS GC Heap Progress...", sw.Elapsed.TotalSeconds);
                        }
                        lastJSUpdate = sw.Elapsed;
                    }
                };

                log.WriteLine("{0,5:n1}s: Enabling JScript Heap Provider", sw.Elapsed.TotalSeconds);
                session.EnableProvider(JSDumpHeapTraceEventParser.ProviderGuid, TraceEventLevel.Informational,
                                       (ulong)JSDumpHeapTraceEventParser.Keywords.jsdumpheap);

                listening = true;
                if (memoryGraph != null)
                {
                    dumper.SetupCallbacks(memoryGraph, session.Source, processID);
                }
                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);

            // Start the providers and trigger the GCs.
            log.WriteLine("{0,5:n1}s: Requesting a JScript Heap Dump", sw.Elapsed.TotalSeconds);

            // WinBlue (V6.3) does not support the passing of the process ID we used on Win8.
            // Thus we have to drop that so it works everywhere.
            // !TODO captures state for all processes!   This is not so bad because the others are probably
            // suspended and thus do not get dumped.   You could fix this on  WinBlue by using the new ETW
            // process filtering.
            session.CaptureState(JSDumpHeapTraceEventParser.ProviderGuid, (ulong)JSDumpHeapTraceEventParser.Keywords.jsdumpheap);

            for (; ;)
            {
                if (readerTask.Wait(100))
                {
                    break;
                }

                if (!jsDataPresent && sw.Elapsed.TotalSeconds > 5)
                {
                    log.WriteLine("{0,5:n1}s: Assume no JSHeap", sw.Elapsed.TotalSeconds);
                    break;
                }

                if (sw.Elapsed.TotalSeconds > 60)
                {
                    log.WriteLine("{0,5:n1}s: Timed out after 60 seconds", sw.Elapsed.TotalSeconds);
                    break;
                }
                // TODO FIX NOW, time out faster if we seek to be stuck
                if (dumpComplete)
                {
                    break;
                }
            }
            if (jsDataPresent)
            {
                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("Collected {0} JSHeap Bulk Edge Events.", edgeRecords);
        log.WriteLine("[{0,5:n1}s: Done Dumping JScript heap success={1}]", sw.Elapsed.TotalSeconds, dumpComplete);

        return(dumpComplete);
    }