summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/System.Private.CoreLib/System.Private.CoreLib.csproj5
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs21
-rw-r--r--src/System.Private.CoreLib/src/System/Diagnostics/Eventing/DotNETRuntimeEventSource.cs33
-rw-r--r--src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipe.cs19
-rw-r--r--src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeEventDispatcher.cs162
-rw-r--r--src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs7
-rw-r--r--src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipePayloadDecoder.cs183
-rw-r--r--src/System.Private.CoreLib/src/System/Diagnostics/Eventing/XplatEventLogger.cs6
-rw-r--r--src/scripts/genRuntimeEventSources.py37
-rw-r--r--src/vm/ClrEtwAll.man5
-rw-r--r--src/vm/ecalllist.h2
-rw-r--r--src/vm/eventpipe.cpp152
-rw-r--r--src/vm/eventpipe.h22
-rw-r--r--src/vm/eventpipebuffermanager.cpp106
-rw-r--r--src/vm/eventpipebuffermanager.h6
-rw-r--r--src/vm/eventpipeconfiguration.cpp4
-rw-r--r--src/vm/eventpipeconfiguration.h3
-rw-r--r--src/vm/eventpipesession.cpp2
-rw-r--r--src/vm/eventpipesession.h18
-rw-r--r--tests/src/tracing/common/RuntimeEventSource.cs52
-rw-r--r--tests/src/tracing/common/common.csproj1
-rw-r--r--tests/src/tracing/runtimeeventsource/RuntimeEventSourceTest.cs91
-rw-r--r--tests/src/tracing/runtimeeventsource/runtimeeventsource.csproj32
23 files changed, 907 insertions, 62 deletions
diff --git a/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/System.Private.CoreLib.csproj
index 1d522cb658..562da8f6e5 100644
--- a/src/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -438,10 +438,13 @@
<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>
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
index e76653215e..162026754e 100644
--- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs
@@ -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
index 0000000000..8bfac2b04b
--- /dev/null
+++ b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/DotNETRuntimeEventSource.cs
@@ -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
+}
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipe.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipe.cs
index 58091be27f..89528cfd30 100644
--- a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipe.cs
+++ b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipe.cs
@@ -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
index 0000000000..7d08d0db84
--- /dev/null
+++ b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeEventDispatcher.cs
@@ -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
+}
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs
index d18610d922..a5cc756ae6 100644
--- a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs
+++ b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs
@@ -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
index 0000000000..48aa5802dd
--- /dev/null
+++ b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipePayloadDecoder.cs
@@ -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
+}
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/XplatEventLogger.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/XplatEventLogger.cs
index 31cfb50876..54238aff51 100644
--- a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/XplatEventLogger.cs
+++ b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/XplatEventLogger.cs
@@ -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))
{
diff --git a/src/scripts/genRuntimeEventSources.py b/src/scripts/genRuntimeEventSources.py
index 5795a4d22a..1bde74ad8b 100644
--- a/src/scripts/genRuntimeEventSources.py
+++ b/src/scripts/genRuntimeEventSources.py
@@ -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)
diff --git a/src/vm/ClrEtwAll.man b/src/vm/ClrEtwAll.man
index 441f0a0c95..1081477bfb 100644
--- a/src/vm/ClrEtwAll.man
+++ b/src/vm/ClrEtwAll.man
@@ -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>
@@ -3261,7 +3263,7 @@
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>
@@ -6853,6 +6855,7 @@
<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" />
diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h
index 34a4dc007a..e575dedfb0 100644
--- a/src/vm/ecalllist.h
+++ b/src/vm/ecalllist.h
@@ -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
diff --git a/src/vm/eventpipe.cpp b/src/vm/eventpipe.cpp
index fb112db72b..00e740edee 100644
--- a/src/vm/eventpipe.cpp
+++ b/src/vm/eventpipe.cpp
@@ -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
diff --git a/src/vm/eventpipe.h b/src/vm/eventpipe.h
index 2becb5c0b8..1904d45d6c 100644
--- a/src/vm/eventpipe.h
+++ b/src/vm/eventpipe.h
@@ -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
diff --git a/src/vm/eventpipebuffermanager.cpp b/src/vm/eventpipebuffermanager.cpp
index 5f09295e95..060707bb3f 100644
--- a/src/vm/eventpipebuffermanager.cpp
+++ b/src/vm/eventpipebuffermanager.cpp
@@ -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;
diff --git a/src/vm/eventpipebuffermanager.h b/src/vm/eventpipebuffermanager.h
index 87502ed6fc..b72648a764 100644
--- a/src/vm/eventpipebuffermanager.h
+++ b/src/vm/eventpipebuffermanager.h
@@ -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
diff --git a/src/vm/eventpipeconfiguration.cpp b/src/vm/eventpipeconfiguration.cpp
index 74d9f44abc..571b0f11b1 100644
--- a/src/vm/eventpipeconfiguration.cpp
+++ b/src/vm/eventpipeconfiguration.cpp
@@ -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)
diff --git a/src/vm/eventpipeconfiguration.h b/src/vm/eventpipeconfiguration.h
index 0f500bd73a..22b5cf9cc3 100644
--- a/src/vm/eventpipeconfiguration.h
+++ b/src/vm/eventpipeconfiguration.h
@@ -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);
diff --git a/src/vm/eventpipesession.cpp b/src/vm/eventpipesession.cpp
index 7fd7ac11cc..5f6e415662 100644
--- a/src/vm/eventpipesession.cpp
+++ b/src/vm/eventpipesession.cpp
@@ -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(
diff --git a/src/vm/eventpipesession.h b/src/vm/eventpipesession.h
index ba91c60aaa..c8c2e1ff1b 100644
--- a/src/vm/eventpipesession.h
+++ b/src/vm/eventpipesession.h
@@ -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
index 0000000000..8518187d9e
--- /dev/null
+++ b/tests/src/tracing/common/RuntimeEventSource.cs
@@ -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;
+ }
+
+ }
+}
diff --git a/tests/src/tracing/common/common.csproj b/tests/src/tracing/common/common.csproj
index 32a210882c..3fb7cc1b9d 100644
--- a/tests/src/tracing/common/common.csproj
+++ b/tests/src/tracing/common/common.csproj
@@ -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
index 0000000000..94d358515f
--- /dev/null
+++ b/tests/src/tracing/runtimeeventsource/RuntimeEventSourceTest.cs
@@ -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
index 0000000000..e8ab52bc9c
--- /dev/null
+++ b/tests/src/tracing/runtimeeventsource/runtimeeventsource.csproj
@@ -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>