Dispatch Runtime Events to EventListener (#18649)
authorBrian Robbins <brianrob@microsoft.com>
Mon, 2 Jul 2018 22:08:32 +0000 (15:08 -0700)
committerGitHub <noreply@github.com>
Mon, 2 Jul 2018 22:08:32 +0000 (15:08 -0700)
23 files changed:
src/System.Private.CoreLib/System.Private.CoreLib.csproj
src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/DotNETRuntimeEventSource.cs [new file with mode: 0644]
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipe.cs
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeEventDispatcher.cs [new file with mode: 0644]
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipePayloadDecoder.cs [new file with mode: 0644]
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/XplatEventLogger.cs
src/scripts/genRuntimeEventSources.py
src/vm/ClrEtwAll.man
src/vm/ecalllist.h
src/vm/eventpipe.cpp
src/vm/eventpipe.h
src/vm/eventpipebuffermanager.cpp
src/vm/eventpipebuffermanager.h
src/vm/eventpipeconfiguration.cpp
src/vm/eventpipeconfiguration.h
src/vm/eventpipesession.cpp
src/vm/eventpipesession.h
tests/src/tracing/common/RuntimeEventSource.cs [new file with mode: 0644]
tests/src/tracing/common/common.csproj
tests/src/tracing/runtimeeventsource/RuntimeEventSourceTest.cs [new file with mode: 0644]
tests/src/tracing/runtimeeventsource/runtimeeventsource.csproj [new file with mode: 0644]

index 1d522cb..562da8f 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventSource_CoreCLR.cs" />
     <Compile Condition="'$(FeatureXplatEventSource)' == 'true'" Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\XplatEventLogger.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\FrameworkEventSource.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\DotNETRuntimeEventSource.cs" />
+    <Compile Include="$(IntermediateOutputPath)..\eventing\DotNETRuntimeEventSource.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipe.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeEventDispatcher.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeEventProvider.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeMetadataGenerator.cs" />
-    <Compile Include="$(IntermediateOutputPath)..\eventing\DotNETRuntimeEventSource.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipePayloadDecoder.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\TraceLogging\TraceLoggingEventHandleTable.cs" />
   </ItemGroup>
   <ItemGroup>
index e766532..1620267 100644 (file)
@@ -2025,7 +2025,7 @@ namespace System.Diagnostics.Tracing
         }
 
         // helper for writing to all EventListeners attached the current eventSource.  
-        private unsafe void WriteToAllListeners(int eventId, Guid* activityID, Guid* childActivityID, params object[] args)
+        internal unsafe void WriteToAllListeners(int eventId, Guid* activityID, Guid* childActivityID, params object[] args)
         {
             EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this);
             eventCallbackArgs.EventId = eventId;
@@ -3948,6 +3948,13 @@ namespace System.Diagnostics.Tracing
             }
 
             eventSource.SendCommand(this, EventProviderType.None, 0, 0, EventCommand.Update, true, level, matchAnyKeyword, arguments);
+
+#if FEATURE_PERFTRACING
+            if (eventSource.GetType() == typeof(RuntimeEventSource))
+            {
+                EventPipeEventDispatcher.Instance.SendCommand(this, EventCommand.Update, true, level, matchAnyKeyword);
+            }
+#endif // FEATURE_PERFTRACING
         }
         /// <summary>
         /// Disables all events coming from eventSource identified by 'eventSource'.  
@@ -3962,6 +3969,13 @@ namespace System.Diagnostics.Tracing
             }
 
             eventSource.SendCommand(this, EventProviderType.None, 0, 0, EventCommand.Update, false, EventLevel.LogAlways, EventKeywords.None, null);
+
+#if FEATURE_PERFTRACING
+            if (eventSource.GetType() == typeof(RuntimeEventSource))
+            {
+                EventPipeEventDispatcher.Instance.SendCommand(this, EventCommand.Update, false, EventLevel.LogAlways, EventKeywords.None);
+            }
+#endif // FEATURE_PERFTRACING
         }
 
         /// <summary>
@@ -4133,6 +4147,11 @@ namespace System.Diagnostics.Tracing
                     }
                 }
             }
+
+#if FEATURE_PERFTRACING
+            // Remove the listener from the EventPipe dispatcher.
+            EventPipeEventDispatcher.Instance.RemoveEventListener(listenerToRemove);
+#endif // FEATURE_PERFTRACING
         }
 
         /// <summary>
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/DotNETRuntimeEventSource.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/DotNETRuntimeEventSource.cs
new file mode 100644 (file)
index 0000000..8bfac2b
--- /dev/null
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+namespace System.Diagnostics.Tracing
+{
+#if FEATURE_PERFTRACING
+    /// <summary>
+    /// RuntimeEventSource is an EventSource that represents the ETW/EventPipe events emitted by the native runtime.
+    /// Most of RuntimeEventSource is auto-generated by scripts/genRuntimeEventSources.py based on the contents of the Microsoft-Windows-DotNETRuntime provider.
+    /// </summary>
+    internal sealed partial class RuntimeEventSource : EventSource
+    {
+        /// <summary>
+        /// Dispatch a single event with the specified event ID and payload.
+        /// </summary>
+        /// <param name="eventID">The eventID corresponding to the event as defined in the auto-generated portion of the RuntimeEventSource class.</param>
+        /// <param name="payload">A span pointing to the data payload for the event.</param>
+        [NonEvent]
+        internal unsafe void ProcessEvent(uint eventID, ReadOnlySpan<Byte> payload)
+        {
+            // Make sure the eventID is valid.
+            if (eventID >= m_eventData.Length)
+            {
+                return;
+            }
+
+            // Decode the payload.
+            object[] decodedPayloadFields = EventPipePayloadDecoder.DecodePayload(ref m_eventData[eventID], payload);
+            WriteToAllListeners((int)eventID, null, null, decodedPayloadFields);
+        }
+   }
+#endif // FEATURE_PERFTRACING
+}
index 58091be..89528cf 100644 (file)
@@ -7,9 +7,20 @@ using System.Runtime.InteropServices;
 using System.Security;
 using Microsoft.Win32;
 
+#if FEATURE_PERFTRACING
+
 namespace System.Diagnostics.Tracing
 {
     [StructLayout(LayoutKind.Sequential)]
+    internal struct EventPipeEventInstanceData
+    {
+        internal IntPtr ProviderID;
+        internal uint EventID;
+        internal IntPtr Payload;
+        internal uint PayloadLength;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
     internal struct EventPipeProviderConfiguration
     {
         [MarshalAs(UnmanagedType.LPWStr)]
@@ -166,6 +177,9 @@ namespace System.Diagnostics.Tracing
         internal static extern unsafe IntPtr DefineEvent(IntPtr provHandle, uint eventID, long keywords, uint eventVersion, uint level, void *pMetadata, uint metadataLength);
 
         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+        internal static extern IntPtr GetProvider(string providerName);
+
+        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
         internal static extern void DeleteProvider(IntPtr provHandle);
 
         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
@@ -176,5 +190,10 @@ namespace System.Diagnostics.Tracing
 
         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
         internal static extern unsafe void WriteEventData(IntPtr eventHandle, uint eventID, EventProvider.EventData* pEventData, uint dataCount, Guid* activityId, Guid* relatedActivityId);
+
+        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+        internal static extern unsafe bool GetNextEvent(EventPipeEventInstanceData* pInstance);
     }
 }
+
+#endif // FEATURE_PERFTRACING
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeEventDispatcher.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeEventDispatcher.cs
new file mode 100644 (file)
index 0000000..7d08d0d
--- /dev/null
@@ -0,0 +1,162 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Diagnostics.Tracing
+{
+#if FEATURE_PERFTRACING
+    internal sealed class EventPipeEventDispatcher
+    {
+        internal sealed class EventListenerSubscription
+        {
+            internal EventKeywords MatchAnyKeywords { get; private set; }
+            internal EventLevel Level { get; private set; }
+
+            internal EventListenerSubscription(EventKeywords matchAnyKeywords, EventLevel level)
+            {
+                MatchAnyKeywords = matchAnyKeywords;
+                Level = level;
+            }
+        }
+
+        internal static readonly EventPipeEventDispatcher Instance = new EventPipeEventDispatcher();
+
+        private IntPtr m_RuntimeProviderID;
+
+        private bool m_stopDispatchTask;
+        private Task m_dispatchTask = null;
+        private object m_dispatchControlLock = new object();
+        private Dictionary<EventListener, EventListenerSubscription> m_subscriptions = new Dictionary<EventListener, EventListenerSubscription>();
+
+        private EventPipeEventDispatcher()
+        {
+            // Get the ID of the runtime provider so that it can be used as a filter when processing events.
+            m_RuntimeProviderID = EventPipeInternal.GetProvider(RuntimeEventSource.EventSourceName);
+        }
+
+        internal void SendCommand(EventListener eventListener, EventCommand command, bool enable, EventLevel level, EventKeywords matchAnyKeywords)
+        {
+            if (command == EventCommand.Update && enable)
+            {
+                lock (m_dispatchControlLock)
+                {
+                    // Add the new subscription.  This will overwrite an existing subscription for the listener if one exists.
+                    m_subscriptions[eventListener] = new EventListenerSubscription(matchAnyKeywords, level);
+
+                    // Commit the configuration change.
+                    CommitDispatchConfiguration();
+                }
+            }
+            else if (command == EventCommand.Update && !enable)
+            {
+                RemoveEventListener(eventListener);
+            }
+        }
+
+        internal void RemoveEventListener(EventListener listener)
+        {
+            lock (m_dispatchControlLock)
+            {
+                // Remove the event listener from the list of subscribers.
+                if (m_subscriptions.ContainsKey(listener))
+                {
+                    m_subscriptions.Remove(listener);
+                }
+
+                // Commit the configuration change.
+                CommitDispatchConfiguration();
+            }
+        }
+
+        private void CommitDispatchConfiguration()
+        {
+            // Ensure that the dispatch task is stopped.
+            // This is a no-op if the task is already stopped.
+            StopDispatchTask();
+
+            // Stop tracing.
+            // This is a no-op if it's already disabled.
+            EventPipeInternal.Disable();
+
+            // Check to see if tracing should be enabled.
+            if (m_subscriptions.Count <= 0)
+            {
+                return;
+            }
+
+            // Start collecting events.
+            EventKeywords aggregatedKeywords = EventKeywords.None;
+            EventLevel highestLevel = EventLevel.LogAlways;
+
+            foreach (EventListenerSubscription subscription in m_subscriptions.Values)
+            {
+                aggregatedKeywords |= subscription.MatchAnyKeywords;
+                highestLevel = (subscription.Level > highestLevel) ? subscription.Level : highestLevel;
+            }
+
+            EventPipeProviderConfiguration[] providerConfiguration = new EventPipeProviderConfiguration[]
+            {
+                new EventPipeProviderConfiguration(RuntimeEventSource.EventSourceName, (ulong) aggregatedKeywords, (uint) highestLevel)
+            };
+
+            EventPipeInternal.Enable(null, 1024, 1, providerConfiguration, 1);
+
+            // Start the dispatch task.
+            StartDispatchTask();
+        }
+
+        private void StartDispatchTask()
+        {
+            Debug.Assert(Monitor.IsEntered(m_dispatchControlLock));
+
+            if (m_dispatchTask == null)
+            {
+                m_stopDispatchTask = false;
+                m_dispatchTask = Task.Factory.StartNew(DispatchEventsToEventListeners, TaskCreationOptions.LongRunning);
+            }
+        }
+
+        private void StopDispatchTask()
+        {
+            Debug.Assert(Monitor.IsEntered(m_dispatchControlLock));
+
+            if(m_dispatchTask != null)
+            {
+                m_stopDispatchTask = true;
+                m_dispatchTask.Wait();
+                m_dispatchTask = null;
+            }
+        }
+
+        private unsafe void DispatchEventsToEventListeners()
+        {
+            // Struct to fill with the call to GetNextEvent.
+            EventPipeEventInstanceData instanceData;
+
+            while (!m_stopDispatchTask)
+            {
+                // Get the next event.
+                while (!m_stopDispatchTask && EventPipeInternal.GetNextEvent(&instanceData))
+                {
+                    // Filter based on provider.
+                    if (instanceData.ProviderID == m_RuntimeProviderID)
+                    {
+                        // Dispatch the event.
+                        ReadOnlySpan<Byte> payload = new ReadOnlySpan<byte>((void*)instanceData.Payload, (int)instanceData.PayloadLength);
+                        RuntimeEventSource.Log.ProcessEvent(instanceData.EventID, payload);
+                    }
+                }
+
+                // Wait for more events.
+                if (!m_stopDispatchTask)
+                {
+                    Thread.Sleep(10);
+                }
+            }
+        }
+    }
+#endif // FEATURE_PERFTRACING
+}
index d18610d..a5cc756 100644 (file)
@@ -384,6 +384,13 @@ namespace System.Diagnostics.Tracing
             if (parameterType == typeof(Guid)) // Guid is not a part of TypeCode enum
                 return GuidTypeCode;
 
+            // IntPtr and UIntPtr are converted to their non-pointer types.
+            if (parameterType == typeof(IntPtr))
+                return IntPtr.Size == 4 ? TypeCode.Int32 : TypeCode.Int64;
+
+            if (parameterType == typeof(UIntPtr))
+                return UIntPtr.Size == 4 ? TypeCode.UInt32 : TypeCode.UInt64;
+
             return Type.GetTypeCode(parameterType);
         }
     }
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipePayloadDecoder.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipePayloadDecoder.cs
new file mode 100644 (file)
index 0000000..48aa580
--- /dev/null
@@ -0,0 +1,183 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace System.Diagnostics.Tracing
+{
+#if FEATURE_PERFTRACING
+    internal static class EventPipePayloadDecoder
+    {
+        /// <summary>
+        /// Given the metadata for an event and an event payload, decode and deserialize the event payload.
+        /// </summary>
+        internal static object[] DecodePayload(ref EventSource.EventMetadata metadata, ReadOnlySpan<Byte> payload)
+        {
+            ParameterInfo[] parameters = metadata.Parameters;
+            object[] decodedFields = new object[parameters.Length];
+            for (int i = 0; i < parameters.Length; i++)
+            {
+                // It is possible that an older version of the event was emitted.
+                // If this happens, the payload might be missing arguments at the end.
+                // We can just leave these unset.
+                if (payload.Length <= 0)
+                {
+                    break;
+                }
+
+                Type parameterType = parameters[i].ParameterType;
+                if (parameterType == typeof(IntPtr))
+                {
+                    if (IntPtr.Size == 8)
+                    {
+                        // Payload is automatically updated to point to the next piece of data.
+                        decodedFields[i] = (IntPtr)ReadUnalignedUInt64(ref payload);
+                    }
+                    else if (IntPtr.Size == 4)
+                    {
+                        decodedFields[i] = (IntPtr)MemoryMarshal.Read<Int32>(payload);
+                        payload = payload.Slice(IntPtr.Size);
+                    }
+                    else
+                    {
+                        Debug.Assert(false, "Unsupported pointer size.");
+                    }
+                }
+                else if (parameterType == typeof(int))
+                {
+                    decodedFields[i] = MemoryMarshal.Read<int>(payload);
+                    payload = payload.Slice(sizeof(int));
+                }
+                else if (parameterType == typeof(uint))
+                {
+                    decodedFields[i] = MemoryMarshal.Read<uint>(payload);
+                    payload = payload.Slice(sizeof(uint));
+                }
+                else if (parameterType == typeof(long))
+                {
+                    // Payload is automatically updated to point to the next piece of data.
+                    decodedFields[i] = (long)ReadUnalignedUInt64(ref payload);
+                }
+                else if (parameterType == typeof(ulong))
+                {
+                    // Payload is automatically updated to point to the next piece of data.
+                    decodedFields[i] = ReadUnalignedUInt64(ref payload);
+                }
+                else if (parameterType == typeof(byte))
+                {
+                    decodedFields[i] = MemoryMarshal.Read<byte>(payload);
+                    payload = payload.Slice(sizeof(byte));
+                }
+                else if (parameterType == typeof(sbyte))
+                {
+                    decodedFields[i] = MemoryMarshal.Read<sbyte>(payload);
+                    payload = payload.Slice(sizeof(sbyte));
+                }
+                else if (parameterType == typeof(short))
+                {
+                    decodedFields[i] = MemoryMarshal.Read<short>(payload);
+                    payload = payload.Slice(sizeof(short));
+                }
+                else if (parameterType == typeof(ushort))
+                {
+                    decodedFields[i] = MemoryMarshal.Read<ushort>(payload);
+                    payload = payload.Slice(sizeof(ushort));
+                }
+                else if (parameterType == typeof(float))
+                {
+                    decodedFields[i] = MemoryMarshal.Read<float>(payload);
+                    payload = payload.Slice(sizeof(float));
+                }
+                else if (parameterType == typeof(double))
+                {
+                    // Payload is automatically updated to point to the next piece of data.
+                    Int64 doubleBytes = (Int64)ReadUnalignedUInt64(ref payload);
+                    decodedFields[i] = BitConverter.Int64BitsToDouble(doubleBytes);
+                }
+                else if (parameterType == typeof(bool))
+                {
+                    // The manifest defines a bool as a 32bit type (WIN32 BOOL), not 1 bit as CLR Does.
+                    decodedFields[i] = (MemoryMarshal.Read<int>(payload) == 1);
+                    payload = payload.Slice(sizeof(int));
+                }
+                else if (parameterType == typeof(Guid))
+                {
+                    // Payload is automatically updated to point to the next piece of data.
+                    decodedFields[i] = ReadUnalignedGuid(ref payload);
+                }
+                else if (parameterType == typeof(char))
+                {
+                    decodedFields[i] = MemoryMarshal.Read<char>(payload);
+                    payload = payload.Slice(sizeof(char));
+                }
+                else if (parameterType == typeof(string))
+                {
+                    ReadOnlySpan<char> charPayload = MemoryMarshal.Cast<byte, char>(payload);
+                    int charCount = 0;
+                    foreach(char c in charPayload)
+                    {
+                        if (c == '\0')
+                            break;
+                        charCount++;
+                    }
+                    string val = new string(charPayload.ToArray(), 0, charCount);
+                    payload = payload.Slice((val.Length + 1) * sizeof(char));
+                    decodedFields[i] = val;
+                }
+                else
+                {
+                    Debug.Assert(false, "Unsupported type encountered.");
+                }
+            }
+
+            return decodedFields;
+        }
+
+        private static UInt64 ReadUnalignedUInt64(ref ReadOnlySpan<byte> payload)
+        {
+            UInt64 val = 0;
+            if (BitConverter.IsLittleEndian)
+            {
+                val |= MemoryMarshal.Read<UInt32>(payload);
+                payload = payload.Slice(sizeof(UInt32));
+                val |= (MemoryMarshal.Read<UInt32>(payload) << sizeof(UInt32));
+                payload = payload.Slice(sizeof(UInt32));
+            }
+            else
+            {
+                val |= (MemoryMarshal.Read<UInt32>(payload) << sizeof(UInt32));
+                payload = payload.Slice(sizeof(UInt32));
+                val |= MemoryMarshal.Read<UInt32>(payload);
+                payload = payload.Slice(sizeof(UInt32));
+            }
+
+            return val;
+        }
+
+        private static Guid ReadUnalignedGuid(ref ReadOnlySpan<byte> payload)
+        {
+            const int sizeOfGuid = 16;
+            byte[] guidBytes = new byte[sizeOfGuid];
+            if (BitConverter.IsLittleEndian)
+            {
+                for (int i = sizeOfGuid - 1; i >= 0; i--)
+                {
+                    guidBytes[i] = MemoryMarshal.Read<byte>(payload);
+                    payload = payload.Slice(sizeof(byte));
+                }
+            }
+            else
+            {
+                for (int i = 0; i < sizeOfGuid; i++)
+                {
+                    guidBytes[i] = MemoryMarshal.Read<byte>(payload);
+                    payload = payload.Slice(sizeof(byte));
+                }
+            }
+
+            return new Guid(guidBytes);
+        }
+    }
+#endif // FEATURE_PERFTRACING
+}
index 31cfb50..54238af 100644 (file)
@@ -156,6 +156,12 @@ namespace System.Diagnostics.Tracing
 
         internal protected  override void OnEventSourceCreated(EventSource eventSource)
         {
+            // Don't enable forwarding of RuntimeEventSource events.`
+            if (eventSource.GetType() == typeof(RuntimeEventSource))
+            {
+                return;
+            }
+
             string eventSourceFilter = eventSourceNameFilter.Value;
             if (string.IsNullOrEmpty(eventSourceFilter) || (eventSource.Name.IndexOf(eventSourceFilter, StringComparison.OrdinalIgnoreCase) >= 0))
             {   
index 5795a4d..1bde74a 100644 (file)
@@ -39,7 +39,7 @@ manifestTypeToCSharpTypeMap = {
     "win:UInt32" : "UInt32",
     "win:UInt64" : "UInt64",
     "win:Int32" : "Int32",
-    "win:Pointer" : "UIntPtr",
+    "win:Pointer" : "IntPtr",
     "win:UnicodeString" : "string",
     "win:Binary" : "byte[]",
     "win:Double" : "double",
@@ -116,7 +116,7 @@ def generateEvent(eventNode, providerNode, outputFile, stringTable):
         # Calculate the number of arguments.
         for argumentNode in argumentNodes:
             if argumentNode.nodeName == "data":
-                if argumentNode.getAttribute("inType") != "win:Binary" and argumentNode.getAttribute("inType") != "win:AnsiString":
+                if argumentNode.getAttribute("inType") != "win:Binary" and argumentNode.getAttribute("inType") != "win:AnsiString" and argumentNode.getAttribute("count") == "":
                     argumentCount += 1
                 else:
                     break
@@ -181,9 +181,26 @@ def generateEvents(providerNode, outputFile, stringTable):
         eventsNode = node
         break
 
+    # Get the list of event nodes.
+    eventNodes = eventsNode.getElementsByTagName("event")
+
+    # Build a list of events to be emitted.  This is where old versions of events are stripped.
+    # key = eventID, value = version
+    eventList = dict()
+    for eventNode in eventNodes:
+        eventID = eventNode.getAttribute("value")
+        eventVersion = eventNode.getAttribute("version")
+        eventList[eventID] = eventVersion
+
     # Iterate over each event node and process it.
-    for eventNode in eventsNode.getElementsByTagName("event"):
-        generateEvent(eventNode, providerNode, outputFile, stringTable)
+    # Only emit events for the latest version of the event, otherwise EventSource initialization will fail.
+    for eventNode in eventNodes:
+        eventID = eventNode.getAttribute("value")
+        eventVersion = eventNode.getAttribute("version")
+        if eventID in eventList and eventList[eventID] == eventVersion:
+            generateEvent(eventNode, providerNode, outputFile, stringTable)
+        elif eventID not in eventList:
+            raise ValueError("eventID could not be found in the list of events to emit.", eventID)
 
 def generateValueMapEnums(providerNode, outputFile, stringTable, enumTypeMap):
 
@@ -355,11 +372,19 @@ namespace System.Diagnostics.Tracing
 """
             writeOutput(outputFile, header)
             increaseTabLevel()
-            writeOutput(outputFile, "[EventSource(Name = \"" + providerName + "\", Guid = \"" + providerNode.getAttribute("guid") + "\")]\n")
-            writeOutput(outputFile, "internal sealed unsafe class " + providerNameToClassNameMap[providerName] + " : EventSource\n")
+
+            className = providerNameToClassNameMap[providerName]
+            writeOutput(outputFile, "[EventSource(Name = \"" + providerName + "\")]\n")
+            writeOutput(outputFile, "internal sealed partial class " + className + " : EventSource\n")
             writeOutput(outputFile, "{\n")
             increaseTabLevel()
 
+            # Create a static property for the EventSource name so that we don't have to initialize the EventSource to get its name.
+            writeOutput(outputFile, "internal const string EventSourceName = \"" + providerName + "\";\n")
+
+            # Write the static Log property.
+            writeOutput(outputFile, "internal static " + className + " Log = new " + className + "();\n\n")
+
             # Write the keywords class.
             generateKeywordsClass(providerNode, outputFile)
 
index 441f0a0..1081477 100644 (file)
@@ -75,6 +75,8 @@
                              message="$(string.RuntimePublisher.MonitoringKeywordMessage)" symbol="CLR_MONITORING_KEYWORD" />
                     <keyword name="CodeSymbolsKeyword" mask="0x400000000"
                              message="$(string.RuntimePublisher.CodeSymbolsKeywordMessage)" symbol="CLR_CODESYMBOLS_KEYWORD" />
+                    <keyword name="EventSourceKeyword" mask="0x800000000"
+                             message="$(string.RuntimePublisher.EventSourceKeywordMessage)" symbol="CLR_EVENTSOURCE_KEYWORD" />
                 </keywords>
                 <!--Tasks-->
                 <tasks>
                            symbol="CodeSymbols" message="$(string.RuntimePublisher.CodeSymbolsEventMessage)"/>
                     
                    <event value="270" version="0" level="win:Informational"  template="EventSource"
-                           opcode="win:Start"
+                           keywords="EventSourceKeyword"
                            symbol="EventSource" />
                 </events>
             </provider>
                 <string id="RuntimePublisher.DebuggerKeywordMessage" value="Debugger" />
                 <string id="RuntimePublisher.MonitoringKeywordMessage" value="Monitoring" />
                 <string id="RuntimePublisher.CodeSymbolsKeywordMessage" value="CodeSymbols" />
+                <string id="RuntimePublisher.EventSourceKeywordMessage" value="EventSource" />
                 <string id="RundownPublisher.LoaderKeywordMessage" value="Loader" />
                 <string id="RundownPublisher.JitKeywordMessage" value="Jit" />
                 <string id="RundownPublisher.JittedMethodILToNativeMapRundownKeywordMessage" value="JittedMethodILToNativeMapRundown" />
index 34a4dc0..e575ded 100644 (file)
@@ -1181,8 +1181,10 @@ FCFuncStart(gEventPipeInternalFuncs)
     QCFuncElement("DefineEvent", EventPipeInternal::DefineEvent)
     QCFuncElement("DeleteProvider", EventPipeInternal::DeleteProvider)
     QCFuncElement("EventActivityIdControl", EventPipeInternal::EventActivityIdControl)
+    QCFuncElement("GetProvider", EventPipeInternal::GetProvider)
     QCFuncElement("WriteEvent", EventPipeInternal::WriteEvent)
     QCFuncElement("WriteEventData", EventPipeInternal::WriteEventData)
+    QCFuncElement("GetNextEvent", EventPipeInternal::GetNextEvent)
 FCFuncEnd()
 #endif // FEATURE_PERFTRACING
 
index fb112db..00e740e 100644 (file)
@@ -221,6 +221,7 @@ void EventPipe::EnableOnStartup()
 
         // Create a new session.
         EventPipeSession *pSession = new EventPipeSession(
+            EventPipeSessionType::File,
             1024 /* 1 GB circular buffer */,
             NULL, /* pProviders */
             0 /* numProviders */);
@@ -293,7 +294,11 @@ void EventPipe::Enable(
     CONTRACTL_END;
 
     // Create a new session.
-    EventPipeSession *pSession = s_pConfig->CreateSession(circularBufferSizeInMB, pProviders, static_cast<unsigned int>(numProviders));
+    EventPipeSession *pSession = s_pConfig->CreateSession(
+        (strOutputPath != NULL) ? EventPipeSessionType::File : EventPipeSessionType::Streaming,
+        circularBufferSizeInMB,
+        pProviders,
+        static_cast<unsigned int>(numProviders));
 
     // Enable the session.
     Enable(strOutputPath, pSession);
@@ -329,8 +334,13 @@ void EventPipe::Enable(LPCWSTR strOutputPath, EventPipeSession *pSession)
     CrstHolder _crst(GetLock());
 
     // Create the event pipe file.
-    SString eventPipeFileOutputPath(strOutputPath);
-    s_pFile = new EventPipeFile(eventPipeFileOutputPath);
+    // A NULL output path means that we should not write the results to a file.
+    // This is used in the EventListener streaming case.
+    if (strOutputPath != NULL)
+    {
+        SString eventPipeFileOutputPath(strOutputPath);
+        s_pFile = new EventPipeFile(eventPipeFileOutputPath);
+    }
 
 #ifdef _DEBUG
     if((CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EnableEventPipe) & 2) == 2)
@@ -395,39 +405,39 @@ void EventPipe::Disable()
         FlushProcessWriteBuffers();
 
         // Write to the file.
-        LARGE_INTEGER disableTimeStamp;
-        QueryPerformanceCounter(&disableTimeStamp);
-        s_pBufferManager->WriteAllBuffersToFile(s_pFile, disableTimeStamp);
-
-        if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeRundown) > 0)
+        if(s_pFile != NULL)
         {
-            // Before closing the file, do rundown.
-            const unsigned int numRundownProviders = 2;
-            EventPipeProviderConfiguration rundownProviders[] =
-            {
-                { W("Microsoft-Windows-DotNETRuntime"), 0x80020138, static_cast<unsigned int>(EventPipeEventLevel::Verbose) }, // Public provider.
-                { W("Microsoft-Windows-DotNETRuntimeRundown"), 0x80020138, static_cast<unsigned int>(EventPipeEventLevel::Verbose) } // Rundown provider.
-            };
-            // The circular buffer size doesn't matter because all events are written synchronously during rundown.
-            s_pSession = s_pConfig->CreateSession(1 /* circularBufferSizeInMB */, rundownProviders, numRundownProviders);
-            s_pConfig->EnableRundown(s_pSession);
-
-            // Ask the runtime to emit rundown events.
-            if(g_fEEStarted && !g_fEEShutDown)
+            LARGE_INTEGER disableTimeStamp;
+            QueryPerformanceCounter(&disableTimeStamp);
+            s_pBufferManager->WriteAllBuffersToFile(s_pFile, disableTimeStamp);
+
+            if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeRundown) > 0)
             {
-                ETW::EnumerationLog::EndRundown();
-            }
+                // Before closing the file, do rundown.
+                const unsigned int numRundownProviders = 2;
+                EventPipeProviderConfiguration rundownProviders[] =
+                {
+                    { W("Microsoft-Windows-DotNETRuntime"), 0x80020138, static_cast<unsigned int>(EventPipeEventLevel::Verbose) }, // Public provider.
+                    { W("Microsoft-Windows-DotNETRuntimeRundown"), 0x80020138, static_cast<unsigned int>(EventPipeEventLevel::Verbose) } // Rundown provider.
+                };
+                // The circular buffer size doesn't matter because all events are written synchronously during rundown.
+                s_pSession = s_pConfig->CreateSession(EventPipeSessionType::File, 1 /* circularBufferSizeInMB */, rundownProviders, numRundownProviders);
+                s_pConfig->EnableRundown(s_pSession);
+
+                // Ask the runtime to emit rundown events.
+                if(g_fEEStarted && !g_fEEShutDown)
+                {
+                    ETW::EnumerationLog::EndRundown();
+                }
 
-            // Disable the event pipe now that rundown is complete.
-            s_pConfig->Disable(s_pSession);
+                // Disable the event pipe now that rundown is complete.
+                s_pConfig->Disable(s_pSession);
 
-            // Delete the rundown session.
-            s_pConfig->DeleteSession(s_pSession);
-            s_pSession = NULL;
-        }
+                // Delete the rundown session.
+                s_pConfig->DeleteSession(s_pSession);
+                s_pSession = NULL;
+            }
 
-        if(s_pFile != NULL)
-        {
             delete(s_pFile);
             s_pFile = NULL;
         }
@@ -486,6 +496,25 @@ EventPipeProvider* EventPipe::CreateProvider(const SString &providerName, EventP
 
 }
 
+EventPipeProvider* EventPipe::GetProvider(const SString &providerName)
+{
+    CONTRACTL
+    {
+        THROWS;
+        GC_NOTRIGGER;
+        MODE_ANY;
+    }
+    CONTRACTL_END;
+
+    EventPipeProvider *pProvider = NULL;
+    if (s_pConfig != NULL)
+    {
+        pProvider = s_pConfig->GetProvider(providerName);
+    }
+
+    return pProvider;
+}
+
 void EventPipe::DeleteProvider(EventPipeProvider *pProvider)
 {
     CONTRACTL
@@ -975,6 +1004,28 @@ void EventPipe::SaveCommandLine(LPCWSTR pwzAssemblyPath, int argc, LPCWSTR *argv
 #endif
 }
 
+EventPipeEventInstance* EventPipe::GetNextEvent()
+{
+    CONTRACTL
+    {
+        THROWS;
+        GC_TRIGGERS;
+        MODE_PREEMPTIVE;
+    }
+    CONTRACTL_END;
+
+    EventPipeEventInstance *pInstance = NULL;
+
+    // Only fetch the next event if a tracing session exists.
+    // The buffer manager is not disposed until the process is shutdown.
+    if (s_pSession != NULL)
+    {
+        pInstance = s_pBufferManager->GetNextEvent();
+    }
+
+    return pInstance;
+}
+
 void QCALLTYPE EventPipeInternal::Enable(
         __in_z LPCWSTR outputFile,
         UINT32 circularBufferSizeInMB,
@@ -1041,6 +1092,22 @@ INT_PTR QCALLTYPE EventPipeInternal::DefineEvent(
     return reinterpret_cast<INT_PTR>(pEvent);
 }
 
+INT_PTR QCALLTYPE EventPipeInternal::GetProvider(
+    __in_z LPCWSTR providerName)
+{
+    QCALL_CONTRACT;
+
+    EventPipeProvider *pProvider = NULL;
+
+    BEGIN_QCALL;
+    
+    pProvider = EventPipe::GetProvider(providerName);
+
+    END_QCALL;
+
+    return reinterpret_cast<INT_PTR>(pProvider);
+}
+
 void QCALLTYPE EventPipeInternal::DeleteProvider(
     INT_PTR provHandle)
 {
@@ -1153,4 +1220,27 @@ void QCALLTYPE EventPipeInternal::WriteEventData(
     END_QCALL;
 }
 
+bool QCALLTYPE EventPipeInternal::GetNextEvent(
+    EventPipeEventInstanceData *pInstance)
+{
+    QCALL_CONTRACT;
+
+    EventPipeEventInstance *pNextInstance = NULL;
+    BEGIN_QCALL;
+
+    _ASSERTE(pInstance != NULL);
+
+    pNextInstance = EventPipe::GetNextEvent();
+    if (pNextInstance)
+    {
+        pInstance->ProviderID = pNextInstance->GetEvent()->GetProvider();
+        pInstance->EventID = pNextInstance->GetEvent()->GetEventID();
+        pInstance->Payload = pNextInstance->GetData();
+        pInstance->PayloadLength = pNextInstance->GetDataLength();
+    }
+
+    END_QCALL;
+    return pNextInstance != NULL;
+} 
+
 #endif // FEATURE_PERFTRACING
index 2becb5c..1904d45 100644 (file)
@@ -12,6 +12,7 @@ class CrstStatic;
 class CrawlFrame;
 class EventPipeConfiguration;
 class EventPipeEvent;
+class EventPipeEventInstance;
 class EventPipeFile;
 class EventPipeJsonFile;
 class EventPipeBuffer;
@@ -250,6 +251,9 @@ class EventPipe
         // Create a provider.
         static EventPipeProvider* CreateProvider(const SString &providerName, EventPipeCallback pCallbackFunction = NULL, void *pCallbackData = NULL);
 
+        // Get a provider.
+        static EventPipeProvider* GetProvider(const SString &providerName);
+
         // Delete a provider.
         static void DeleteProvider(EventPipeProvider *pProvider);
 
@@ -273,6 +277,9 @@ class EventPipe
         // Save the command line for the current process.
         static void SaveCommandLine(LPCWSTR pwzAssemblyPath, int argc, LPCWSTR *argv);
 
+        // Get next event.
+        static EventPipeEventInstance* GetNextEvent();
+
     protected:
 
         // The counterpart to WriteEvent which after the payload is constructed
@@ -373,6 +380,15 @@ private:
         EVENT_ACTIVITY_CONTROL_CREATE_SET_ID = 5
     };
 
+    struct EventPipeEventInstanceData
+    {
+    public:
+        void *ProviderID;
+        unsigned int EventID;
+        const BYTE *Payload;
+        unsigned int PayloadLength;
+    };
+
 public:
 
     static void QCALLTYPE Enable(
@@ -397,6 +413,9 @@ public:
         void *pMetadata,
         UINT32 metadataLength);
 
+    static INT_PTR QCALLTYPE GetProvider(
+        __in_z LPCWSTR providerName);
+
     static void QCALLTYPE DeleteProvider(
         INT_PTR provHandle);
 
@@ -417,6 +436,9 @@ public:
         EventData *pEventData,
         UINT32 eventDataCount,
         LPCGUID pActivityId, LPCGUID pRelatedActivityId);
+
+    static bool QCALLTYPE GetNextEvent(
+        EventPipeEventInstanceData *pInstance); 
 };
 
 #endif // FEATURE_PERFTRACING
index 5f09295..060707b 100644 (file)
@@ -29,6 +29,7 @@ EventPipeBufferManager::EventPipeBufferManager()
     m_numBuffersStolen = 0;
     m_numBuffersLeaked = 0;
     m_numEventsStored = 0;
+    m_numEventsDropped = 0;
     m_numEventsWritten = 0;
 #endif // _DEBUG
 }
@@ -76,7 +77,7 @@ EventPipeBufferManager::~EventPipeBufferManager()
     }
 }
 
-EventPipeBuffer* EventPipeBufferManager::AllocateBufferForThread(Thread *pThread, unsigned int requestSize)
+EventPipeBuffer* EventPipeBufferManager::AllocateBufferForThread(EventPipeSession &session, Thread *pThread, unsigned int requestSize)
 {
     CONTRACTL
     {
@@ -133,11 +134,14 @@ EventPipeBuffer* EventPipeBufferManager::AllocateBufferForThread(Thread *pThread
         }
     }
 
+    // Only steal buffers from other threads if the session being written to is a
+    // file-based session.  Streaming sessions will simply drop events.
+    // TODO: Add dropped events telemetry here.
     EventPipeBuffer *pNewBuffer = NULL;
-    if(!allocateNewBuffer)
+    if(!allocateNewBuffer && (session.GetSessionType() == EventPipeSessionType::File))
     {
         // We can't allocate a new buffer.
-        // Find the oldest buffer, zero it, and re-purpose it for this thread.
+        // Find the oldest buffer, de-allocate it, and re-purpose it for this thread.
 
         // Find the thread that contains the oldest stealable buffer, and get its list of buffers.
         EventPipeBufferList *pListToStealFrom = FindThreadToStealFrom();
@@ -179,7 +183,7 @@ EventPipeBuffer* EventPipeBufferManager::AllocateBufferForThread(Thread *pThread
         // Pick the base buffer size based.  Debug builds have a smaller size to stress the allocate/steal path more.
         unsigned int baseBufferSize =
 #ifdef _DEBUG
-            5 * 1024; // 5K
+            30 * 1024; // 30K
 #else
             100 * 1024; // 100K
 #endif
@@ -192,6 +196,13 @@ EventPipeBuffer* EventPipeBufferManager::AllocateBufferForThread(Thread *pThread
             bufferSize = requestSize;
         }
 
+        // Don't allow the buffer size to exceed 1MB.
+        const unsigned int maxBufferSize = 1024 * 1024;
+        if(bufferSize > maxBufferSize)
+        {
+            bufferSize = maxBufferSize;
+        }
+
         // EX_TRY is used here as opposed to new (nothrow) because
         // the constructor also allocates a private buffer, which
         // could throw, and cannot be easily checked
@@ -363,7 +374,7 @@ bool EventPipeBufferManager::WriteEvent(Thread *pThread, EventPipeSession &sessi
         // to switch to preemptive mode here.
 
         unsigned int requestSize = sizeof(EventPipeEventInstance) + payload.GetSize();
-        pBuffer = AllocateBufferForThread(pThread, requestSize);
+        pBuffer = AllocateBufferForThread(session, pThread, requestSize);
     }
 
     // Try to write the event after we allocated (or stole) a buffer.
@@ -382,6 +393,10 @@ bool EventPipeBufferManager::WriteEvent(Thread *pThread, EventPipeSession &sessi
     {
         InterlockedIncrement(&m_numEventsStored);
     }
+    else
+    {
+        InterlockedIncrement(&m_numEventsDropped);
+    }
 #endif // _DEBUG
     return !allocNewBuffer;
 }
@@ -458,6 +473,64 @@ void EventPipeBufferManager::WriteAllBuffersToFile(EventPipeFile *pFile, LARGE_I
     }
 }
 
+EventPipeEventInstance* EventPipeBufferManager::GetNextEvent()
+{
+    CONTRACTL
+    {
+        NOTHROW;
+        GC_NOTRIGGER;
+        MODE_ANY;
+    }
+    CONTRACTL_END;
+
+    // Take the lock before walking the buffer list.
+    SpinLockHolder _slh(&m_lock);
+
+    // Naively walk the circular buffer, getting the event stream in timestamp order.
+    LARGE_INTEGER stopTimeStamp;
+    QueryPerformanceCounter(&stopTimeStamp);
+    while (true)
+    {
+        EventPipeEventInstance *pOldestInstance = NULL;
+        EventPipeBuffer *pOldestContainingBuffer = NULL;
+        EventPipeBufferList *pOldestContainingList = NULL;
+        SListElem<EventPipeBufferList*> *pElem = m_pPerThreadBufferList->GetHead();
+        while (pElem != NULL)
+        {
+            EventPipeBufferList *pBufferList = pElem->GetValue();
+
+            // Peek the next event out of the list.
+            EventPipeBuffer *pContainingBuffer = NULL;
+            EventPipeEventInstance *pNext = pBufferList->PeekNextEvent(stopTimeStamp, &pContainingBuffer);
+            if (pNext != NULL)
+            {
+                // If it's the oldest event we've seen, then save it.
+                if ((pOldestInstance == NULL) ||
+                    (pOldestInstance->GetTimeStamp()->QuadPart > pNext->GetTimeStamp()->QuadPart))
+                {
+                    pOldestInstance = pNext;
+                    pOldestContainingBuffer = pContainingBuffer;
+                    pOldestContainingList = pBufferList;
+                }
+            }
+
+            pElem = m_pPerThreadBufferList->GetNext(pElem);
+        }
+
+        if (pOldestInstance == NULL)
+        {
+            // We're done.  There are no more events.
+            return NULL;
+        }
+
+        // Pop the event from the buffer.
+        pOldestContainingList->PopNextEvent(stopTimeStamp);
+
+        // Return the oldest event that hasn't yet been processed.
+        return pOldestInstance;
+    }
+}
+
 void EventPipeBufferManager::DeAllocateBuffers()
 {
     CONTRACTL
@@ -777,25 +850,22 @@ EventPipeEventInstance* EventPipeBufferList::PopNextEvent(LARGE_INTEGER beforeTi
     EventPipeBuffer *pContainingBuffer = NULL;
     EventPipeEventInstance *pNext = PeekNextEvent(beforeTimeStamp, &pContainingBuffer);
 
-    // If the event is non-NULL, pop it.
-    if(pNext != NULL && pContainingBuffer != NULL)
+    // Check to see if we need to clean-up the buffer that contained the previously popped event.
+    if(pContainingBuffer->GetPrevious() != NULL)
     {
-        pContainingBuffer->PopNext(beforeTimeStamp);
-
-        // If the buffer is not the last buffer in the list and it has been drained, de-allocate it.
-        if((pContainingBuffer->GetNext() != NULL) && (pContainingBuffer->PeekNext(beforeTimeStamp) == NULL))
-        {
-            // This buffer must be the head node of the list.
-            _ASSERTE(pContainingBuffer->GetPrevious() == NULL);
+            // Remove the previous node.  The previous node should always be the head node.
             EventPipeBuffer *pRemoved = GetAndRemoveHead();
-            _ASSERTE(pRemoved == pContainingBuffer);
+            _ASSERTE(pRemoved != pContainingBuffer);
+            _ASSERTE(pContainingBuffer == GetHead());
 
             // De-allocate the buffer.
             m_pManager->DeAllocateBuffer(pRemoved);
+    }
 
-            // Reset the read buffer so that it becomes the head node on next peek or pop operation.
-            m_pReadBuffer = NULL;
-        }
+    // If the event is non-NULL, pop it.
+    if(pNext != NULL && pContainingBuffer != NULL)
+    {
+        pContainingBuffer->PopNext(beforeTimeStamp);
     }
 
     return pNext;
index 87502ed..b72648a 100644 (file)
@@ -43,13 +43,14 @@ private:
     unsigned int m_numBuffersStolen;
     unsigned int m_numBuffersLeaked;
     Volatile<LONG> m_numEventsStored;
+    Volatile<LONG> m_numEventsDropped;
     LONG m_numEventsWritten;
 #endif // _DEBUG
 
     // Allocate a new buffer for the specified thread.
     // This function will store the buffer in the thread's buffer list for future use and also return it here.
     // A NULL return value means that a buffer could not be allocated.
-    EventPipeBuffer* AllocateBufferForThread(Thread *pThread, unsigned int requestSize);
+    EventPipeBuffer* AllocateBufferForThread(EventPipeSession &session, Thread *pThread, unsigned int requestSize);
 
     // Add a buffer to the thread buffer list.
     void AddBufferToThreadBufferList(EventPipeBufferList *pThreadBuffers, EventPipeBuffer *pBuffer);
@@ -82,6 +83,9 @@ public:
     // to free their buffer for a very long time.
     void DeAllocateBuffers();
 
+    // Get next event.  This is used to dispatch events to EventListener.
+    EventPipeEventInstance* GetNextEvent();
+
 #ifdef _DEBUG
     bool EnsureConsistency();
 #endif // _DEBUG
index 74d9f44..571b0f1 100644 (file)
@@ -309,7 +309,7 @@ size_t EventPipeConfiguration::GetCircularBufferSize() const
     return ret;
 }
 
-EventPipeSession* EventPipeConfiguration::CreateSession(unsigned int circularBufferSizeInMB, EventPipeProviderConfiguration *pProviders, unsigned int numProviders)
+EventPipeSession* EventPipeConfiguration::CreateSession(EventPipeSessionType sessionType, unsigned int circularBufferSizeInMB, EventPipeProviderConfiguration *pProviders, unsigned int numProviders)
 {
     CONTRACTL
     {
@@ -319,7 +319,7 @@ EventPipeSession* EventPipeConfiguration::CreateSession(unsigned int circularBuf
     }
     CONTRACTL_END;
 
-    return new EventPipeSession(circularBufferSizeInMB, pProviders, numProviders);
+    return new EventPipeSession(sessionType, circularBufferSizeInMB, pProviders, numProviders);
 }
 
 void EventPipeConfiguration::DeleteSession(EventPipeSession *pSession)
index 0f500bd..22b5cf9 100644 (file)
@@ -15,6 +15,7 @@ class EventPipeEventInstance;
 class EventPipeProvider;
 struct EventPipeProviderConfiguration;
 class EventPipeSession;
+enum class EventPipeSessionType;
 class EventPipeSessionProvider;
 
 enum class EventPipeEventLevel
@@ -53,7 +54,7 @@ public:
     EventPipeProvider* GetProvider(const SString &providerID);
 
     // Create a new session.
-    EventPipeSession* CreateSession(unsigned int circularBufferSizeInMB, EventPipeProviderConfiguration *pProviders, unsigned int numProviders);
+    EventPipeSession* CreateSession(EventPipeSessionType sessionType, unsigned int circularBufferSizeInMB, EventPipeProviderConfiguration *pProviders, unsigned int numProviders);
 
     // Delete a session.
     void DeleteSession(EventPipeSession *pSession);
index 7fd7ac1..5f6e415 100644 (file)
@@ -10,6 +10,7 @@
 #ifdef FEATURE_PERFTRACING
 
 EventPipeSession::EventPipeSession(
+    EventPipeSessionType sessionType,
     unsigned int circularBufferSizeInMB,
     EventPipeProviderConfiguration *pProviders,
     unsigned int numProviders)
@@ -22,6 +23,7 @@ EventPipeSession::EventPipeSession(
     }
     CONTRACTL_END;
 
+    m_sessionType = sessionType;
     m_circularBufferSizeInBytes = circularBufferSizeInMB * 1024 * 1024; // 1MB;
     m_rundownEnabled = false;
     m_pProviderList = new EventPipeSessionProviderList(
index ba91c60..c8c2e1f 100644 (file)
@@ -12,6 +12,12 @@ struct EventPipeProviderConfiguration;
 class EventPipeSessionProviderList;
 class EventPipeSessionProvider;
 
+enum class EventPipeSessionType
+{
+    File,
+    Streaming
+};
+
 class EventPipeSession
 {
 private:
@@ -24,10 +30,15 @@ private:
     // True if rundown is enabled.
     Volatile<bool> m_rundownEnabled;
 
+    // The type of the session.
+    // This determines behavior within the system (e.g. policies around which events to drop, etc.)
+    EventPipeSessionType m_sessionType;
+
 public:
 
     // TODO: This needs to be exposed via EventPipe::CreateSession() and EventPipe::DeleteSession() to avoid memory ownership issues.
     EventPipeSession(
+        EventPipeSessionType sessionType,
         unsigned int circularBufferSizeInMB,
         EventPipeProviderConfiguration *pProviders,
         unsigned int numProviders);
@@ -37,6 +48,13 @@ public:
     // Determine if the session is valid or not.  Invalid sessions can be detected before they are enabled.
     bool IsValid() const;
 
+    // Get the session type.
+    EventPipeSessionType GetSessionType() const
+    {
+        LIMITED_METHOD_CONTRACT;
+        return m_sessionType;
+    }
+
     // Get the configured size of the circular buffer.
     size_t GetCircularBufferSize() const
     {
diff --git a/tests/src/tracing/common/RuntimeEventSource.cs b/tests/src/tracing/common/RuntimeEventSource.cs
new file mode 100644 (file)
index 0000000..8518187
--- /dev/null
@@ -0,0 +1,52 @@
+using System;
+using System.Diagnostics.Tracing;
+using System.Reflection;
+
+namespace Tracing.Tests.Common
+{
+    public static class RuntimeEventSource
+    {
+        private static FieldInfo m_staticLogField;
+
+        public static EventSource Log
+        {
+            get
+            {
+                return (EventSource) m_staticLogField.GetValue(null);
+            }
+        }
+
+        static RuntimeEventSource()
+        {
+            if(!Initialize())
+            {
+                throw new InvalidOperationException("Reflection failed.");
+            }
+        }
+
+        private static bool Initialize()
+        {
+           Assembly SPC = typeof(System.Diagnostics.Tracing.EventSource).Assembly;
+           if(SPC == null)
+           {
+               Console.WriteLine("System.Private.CoreLib assembly == null");
+               return false;
+           }
+           Type runtimeEventSourceType = SPC.GetType("System.Diagnostics.Tracing.RuntimeEventSource");
+           if(runtimeEventSourceType == null)
+           {
+               Console.WriteLine("System.Diagnostics.Tracing.RuntimeEventSource type == null");
+               return false;
+           }
+           m_staticLogField = runtimeEventSourceType.GetField("Log", BindingFlags.NonPublic | BindingFlags.Static);
+           if(m_staticLogField == null)
+           {
+               Console.WriteLine("RuntimeEventSource.Log field == null");
+               return false;
+           }
+
+           return true;
+        }
+
+    }
+}
index 32a2108..3fb7cc1 100644 (file)
@@ -28,6 +28,7 @@
     <Compile Include="Assert.cs" />
     <Compile Include="EtlFile.cs" />
     <Compile Include="NetPerfFile.cs" />
+    <Compile Include="RuntimeEventSource.cs" />
     <Compile Include="TraceControl.cs" />
     <Compile Include="TraceConfiguration.cs" />
   </ItemGroup>
diff --git a/tests/src/tracing/runtimeeventsource/RuntimeEventSourceTest.cs b/tests/src/tracing/runtimeeventsource/RuntimeEventSourceTest.cs
new file mode 100644 (file)
index 0000000..94d3585
--- /dev/null
@@ -0,0 +1,91 @@
+using System;
+using System.IO;
+using System.Diagnostics.Tracing;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using Tracing.Tests.Common;
+
+namespace Tracing.Tests
+{
+    public sealed class RuntimeEventSourceTest
+    {
+        static int Main(string[] args)
+        {
+            // Get the RuntimeEventSource.
+            EventSource eventSource = RuntimeEventSource.Log;
+
+            using (SimpleEventListener noEventsListener = new SimpleEventListener("NoEvents"))
+            {
+                // Enable the provider, but not any keywords, so we should get no events as long as no rundown occurs.
+                noEventsListener.EnableEvents(eventSource, EventLevel.Critical, (EventKeywords)(0));
+
+                // Create an EventListener.
+                using (SimpleEventListener listener = new SimpleEventListener("Simple"))
+                {
+                    // Trigger the allocator task.
+                    System.Threading.Tasks.Task.Run(new Action(Allocator));
+
+                    // Enable events.
+                    listener.EnableEvents(eventSource, EventLevel.Verbose, (EventKeywords)(0x4c14fccbd));
+
+                    // Wait for events.
+                    Thread.Sleep(1000);
+
+                    // Generate some GC events.
+                    GC.Collect(2, GCCollectionMode.Forced);
+
+                    // Wait for more events.
+                    Thread.Sleep(1000);
+
+                    // Ensure that we've seen some events.
+                    Assert.True("listener.EventCount > 0", listener.EventCount > 0);
+                }
+
+                // Generate some more GC events.
+                GC.Collect(2, GCCollectionMode.Forced);
+
+                // Ensure that we've seen no events.
+                Assert.True("noEventsListener.EventCount == 0", noEventsListener.EventCount == 0);
+            }
+
+            return 100;
+        }
+
+        private static void Allocator()
+        {
+            while (true)
+            {
+                for(int i=0; i<1000; i++)
+                    GC.KeepAlive(new object());
+
+                Thread.Sleep(10);
+            }
+        }
+    }
+
+    internal sealed class SimpleEventListener : EventListener
+    {
+        private string m_name;
+
+        public SimpleEventListener(string name)
+        {
+            m_name = name;
+        }
+
+        public int EventCount { get; private set; } = 0;
+
+        protected override void OnEventWritten(EventWrittenEventArgs eventData)
+        {
+            Console.WriteLine($"[{m_name}] ID = {eventData.EventId} Name = {eventData.EventName}");
+            for (int i = 0; i < eventData.Payload.Count; i++)
+            {
+                string payloadString = eventData.Payload[i] != null ? eventData.Payload[i].ToString() : string.Empty;
+                Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
+            }
+            Console.WriteLine("\n");
+
+
+            EventCount++;
+        }
+    }
+}
diff --git a/tests/src/tracing/runtimeeventsource/runtimeeventsource.csproj b/tests/src/tracing/runtimeeventsource/runtimeeventsource.csproj
new file mode 100644 (file)
index 0000000..e8ab52b
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{8E3244CB-407F-4142-BAAB-E7A55901A5FA}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestPriority>0</CLRTestPriority>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="RuntimeEventSourceTest.cs" />
+    <ProjectReference Include="../common/common.csproj" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>