Thursday, July 14, 2016

ETW event listener

using Diagnostics.Tracing;
using Diagnostics.Tracing.Parsers;
using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Threading;
using System.Threading.Tasks;

// This program is a simple demo, it has both a EventSource and a EventGenerator which generate events for 10 seconds
// as well as a 'listener; that listens for these events (potentially in a differnet process, but we don't do that here)
// and does some rudimentary processing (calculates the difference between two particular events.  
namespace SimpleMonitor
{
    using Microsoft.Cloud.InstrumentationFramework;

    // This code belongs in the process generating the events.  They are samples
    [EventSource(Name = "Microsoft-Demos-SimpleMonitor")]     // This is the name of my eventSource outside my program.
    class MyEventSource : EventSource
    {
        // Notice that the bodies of the events follow a pattern:  WriteEvent(ID, <args>) where ID is a unique ID
        // Starting at 1 and giving each different event a unque one, and passing all the payload arguments on to be sent out.
        public void MyFirstEvent(string MyName, int MyId) { WriteEvent(1, MyName, MyId); }
        public void MySecondEvent(int MyId) { WriteEvent(2, MyId); }
        public void Stop() { WriteEvent(3); }

        // Typically you only create one EventSource and use it throughout your program.  Thus a static field makes sense.
        public static MyEventSource Log = new MyEventSource();
    }

    // This code belongs in the process generating the events.   It is in this process for simplicity.
    class EventGenerator
    {
        public static void CreateEvents()
        {
            // This just spanws a thread that generates events every second for 10 seconds than issues a Stop event.
            Task.Factory.StartNew(delegate
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("** Generateing the MyFirst and MySecond events.");
                    MyEventSource.Log.MyFirstEvent("Some string", i);
                    Thread.Sleep(10);
                    MyEventSource.Log.MySecondEvent(i);
                    Thread.Sleep(1000);
                }
                //Console.WriteLine("** Generating the Stop Event.");
                //MyEventSource.Log.Stop();
             
                    string monitoringAccountName = "TestAccount";
                    string monitoringNamespace = "TestNamespace";
                    string metricName = "TestMetric";
                    string[] dimensionNames = new[] {"Name"};
                    string[] dimensionValues = new[] {"Vivek"};
                    //ErrorContext errorContext = new ErrorContext();
                    //MeasureMetric measureMetric = MeasureMetric.Create(
                    //    monitoringAccountName,
                    //    monitoringNamespace,
                    //    metricName,
                    //    true,
                    //    dimensionNames);
                    //measureMetric.LogValue(5, ref errorContext, dimensionValues);
             
            });
        }
    }

    /// <summary>
    /// The main program is the 'listener' that listens and processes the events that come from EventGenerator
    /// </summary>
    class Program
    {
        /// <summary>
        /// This is a demo of using TraceEvent to activate a 'real time' provider that is listening to
        /// the MyEventSource above.   Normally this event source would be in a differnet process,  but
        /// it also works if this process generate the evnets and I do that here for simplicity.
        /// </summary>
        static int Main(string[] args)
        {
            try
            {
                // This is the name of the event source.  
                var providerName = "Microsoft-Demos-SimpleMonitor";
                Debug.Assert(providerName == MyEventSource.Log.Name);
                // Given just the name of the eventSource you can get the GUID for the evenSource by calling this API.
                // From a ETW perspective, the GUID is the 'true name' of the EventSource.
                var providerGuid = TraceEventSession.GetEventSourceGuidFromName(providerName);
                Debug.Assert(providerGuid == MyEventSource.Log.Guid);

                var provider2 = "Test-Event";
                var providerGuid2 = TraceEventSession.GetEventSourceGuidFromName(provider2);

                var provider3 = "IfxMetricsEvent";
                var providerGuid3 = TraceEventSession.GetEventSourceGuidFromName(provider3);

                var provider4 = "Microsoft-Cloud-InstrumentationFramework-Metrics";
                var providerGuid4 = TraceEventSession.GetEventSourceGuidFromName(provider4);

                var provider5 = "MetricEtwEvent";
                var providerGuid5 = TraceEventSession.GetEventSourceGuidFromName(provider5);

                // Today you have to be Admin to turn on ETW events (anyone can write ETW events).  
                if (!(TraceEventSession.IsElevated() ?? false))
                {
                    Console.WriteLine("To turn on ETW events you need to be Administrator, please run from an Admin process.");
                    return -1;
                }

                // As mentioned below, sessions can outlive the process that created them.  Thus you need a way of
                // naming the session so that you can 'reconnect' to it from another process.   This is what the name
                // is for.  It can be anything, but it should be descriptive and unique.   If you expect mulitple versions
                // of your program to run simultaneously, you need to generate unique names (e.g. add a process ID suffix)
                Console.WriteLine("Creating a 'My Session' session");
                var sessionName = "My Session";
                using (var session = new TraceEventSession(sessionName, null)) // the null second parameter means 'real time session'
//                using (var session = new EtwRealtimeConsumer("EtwRealtimeConsumer"))
                {
                    // Note that sessions create a OS object (a session) that lives beyond the lifetime of the process
                    // that created it (like Filles), thus you have to be more careful about always cleaning them up.
                    // An importanty way you can do this is to set the 'StopOnDispose' property which will cause the session to
                    // stop (and thus the OS object will die) when the TraceEventSession dies.   Because we used a 'using'
                    // statement, this means that any exception in the code below will clean up the OS object.  
                    session.StopOnDispose = true;

                    // By default, if you hit Ctrl-C your .NET objects may not be disposed, so force it to.  It is OK if dispose is called twice.
                    Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e) { session.Dispose(); };

                    // prepare to read from the session, connect the ETWTraceEventSource to the session
                    using (var source = new ETWTraceEventSource(sessionName, TraceEventSourceType.Session))
                    {
                        // To demonstrate non-trivial event manipuation, we calculate the time delta between 'MyFirstEvent and 'MySecondEvent'
                        // These variables are used in this calculation
                        int lastMyEventID = int.MinValue; // an illegal value to start with.
                        double lastMyEventMSec = 0;

                        // Hook up the parser that knows about EventSources
                        var parser = new DynamicTraceEventParser(source);
                        parser.All += delegate (TraceEvent data)
                        {
                            Console.WriteLine("GOT EVENT: " + data.ToString());

                            if (true /*data.ProviderGuid == providerGuid */) // We don't actually need this since we only turned one one provider.
                            {
                                // Note that this is the inefficient way of parsing events (there are string comparisions on the
                                // event Name and every payload value), however it is fine for events that occur less than 100 times
                                // a second.   For more volumous events, you should consider making a parser for you eventSource
                                // (covered in another demo).  This makes your code fare less 'reflection-like' where you have lots
                                // of strings (e.g. "MyFirstEvent", "MyId" ...) which is better even ignoring the performance benefit.
                                if (data.EventName == "MyFirstEvent")
                                {
                                    // On First Events, simply remember the ID and time of the event
                                    lastMyEventID = (int)data.PayloadByName("MyId");
                                    lastMyEventMSec = data.TimeStampRelativeMSec;
                                }
                                else if (data.EventName == "MySecondEvent")
                                {
                                    // On Second Events, if the ID matches, compute the delta and display it.
                                    if (lastMyEventID == (int)data.PayloadByName("MyId"))
                                        Console.WriteLine("   > Time Delta from first Event = {0:f3} MSec", data.TimeStampRelativeMSec - lastMyEventMSec);
                                }
                                else if (data.EventName == "Stop")
                                {
                                    // Stop processing after we we see the 'Stop' event
                                    source.Close();
                                }
                            }
                        };

                        // Enable my provider, you can call many of these on the same session to get other events.
                        session.EnableProvider(providerGuid);
                        session.EnableProvider(providerGuid2);
                        session.EnableProvider(providerGuid3);
                        session.EnableProvider(providerGuid4);
                        session.EnableProvider(providerGuid5);
                        session.EnableProvider(new Guid("95595d3e-4dd8-49b1-9f90-85870e95b10b"));
                        //session.EnableProvider(new Guid("edc24920-e004-40f6-a8e1-0e6e48f39d84"));
                        session.EnableProvider(Guid.Parse("{edc24920-e004-40f6-a8e1-0e6e48f39d84}"));

                        // Start another thread that Causes MyEventSource to create some events
                        // Normally this code as well as the EventSource itself would be in a different process.
                        EventGenerator.CreateEvents();

                        Console.WriteLine("Staring Listing for events");
                        // go into a loop processing events can calling the callbacks.  Because this is live data (not from a file)
                        // processing never completes by itself, but only because someone called 'source.Close()'.
                        source.Process();
                        Console.WriteLine();
                        Console.WriteLine("Stopping the collection of events.");


                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            //while (true)
            //{
            //    Thread.Sleep(100);
            //}
            return 0;
        }
    }
}