diff options
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> |