From 6555bd3a1f6e6de0604e3065b4040c3a6ac1e44e Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Mon, 29 Jan 2018 18:49:51 -0800 Subject: [PATCH] Add ActivityId Support to EventPipe (#16055) --- src/mscorlib/System.Private.CoreLib.csproj | 2 +- .../src/System/Diagnostics/Eventing/EventPipe.cs | 3 + .../Diagnostics/Eventing/EventPipeEventProvider.cs | 2 +- .../Diagnostics/Eventing/EventSource_CoreCLR.cs | 52 +++-- src/vm/ecalllist.h | 1 + src/vm/eventpipe.cpp | 67 +++++++ src/vm/eventpipe.h | 14 ++ src/vm/threads.cpp | 1 + src/vm/threads.h | 18 ++ tests/src/tracing/common/Assert.cs | 40 ++++ tests/src/tracing/common/common.csproj | 1 + .../EventActivityIdControl.cs | 221 +++++++++++++++++++++ .../eventactivityidcontrol.csproj | 30 +++ 13 files changed, 431 insertions(+), 21 deletions(-) create mode 100755 tests/src/tracing/common/Assert.cs create mode 100755 tests/src/tracing/eventactivityidcontrol/EventActivityIdControl.cs create mode 100644 tests/src/tracing/eventactivityidcontrol/eventactivityidcontrol.csproj diff --git a/src/mscorlib/System.Private.CoreLib.csproj b/src/mscorlib/System.Private.CoreLib.csproj index a85e298..df394ed 100644 --- a/src/mscorlib/System.Private.CoreLib.csproj +++ b/src/mscorlib/System.Private.CoreLib.csproj @@ -518,7 +518,7 @@ - + diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipe.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipe.cs index 5363b2a..57c3b8f 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipe.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipe.cs @@ -169,6 +169,9 @@ namespace System.Diagnostics.Tracing internal static extern void DeleteProvider(IntPtr provHandle); [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + internal static extern int EventActivityIdControl(uint controlCode, ref Guid activityId); + + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] internal static extern unsafe void WriteEvent(IntPtr eventHandle, uint eventID, void* pData, uint length, Guid* activityId, Guid* relatedActivityId); [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs index a8789f5..0d99ff4 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs @@ -74,7 +74,7 @@ namespace System.Diagnostics.Tracing // Get or set the per-thread activity ID. int IEventProvider.EventActivityIdControl(UnsafeNativeMethods.ManifestEtw.ActivityControl ControlCode, ref Guid ActivityId) { - return 0; + return EventPipeInternal.EventActivityIdControl((uint)ControlCode, ref ActivityId); } // Define an EventPipeEvent handle. diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventSource_CoreCLR.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventSource_CoreCLR.cs index c5e2b20..98556a7 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/EventSource_CoreCLR.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventSource_CoreCLR.cs @@ -11,13 +11,6 @@ namespace System.Diagnostics.Tracing { public partial class EventSource { -#if FEATURE_MANAGED_ETW && FEATURE_PERFTRACING - // For non-Windows, we use a thread-local variable to hold the activity ID. - // On Windows, ETW has it's own thread-local variable and we participate in its use. - [ThreadStatic] - private static Guid s_currentThreadActivityId; -#endif // FEATURE_MANAGED_ETW && FEATURE_PERFTRACING - // ActivityID support (see also WriteEventWithRelatedActivityIdCore) /// /// When a thread starts work that is on behalf of 'something else' (typically another @@ -47,13 +40,25 @@ namespace System.Diagnostics.Tracing // We ignore errors to keep with the convention that EventSources do not throw errors. // Note we can't access m_throwOnWrites because this is a static method. -#if FEATURE_PERFTRACING - s_currentThreadActivityId = activityId; +#if FEATURE_PERFTRACING && PLATFORM_WINDOWS + // Set the activity id via EventPipe. + EventPipeInternal.EventActivityIdControl( + (uint)UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID, + ref activityId); + + // Set the activity id via ETW and fetch the previous id. + if (UnsafeNativeMethods.ManifestEtw.EventActivityIdControl( + UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, + ref activityId) == 0) +#elif FEATURE_PERFTRACING + if (EventPipeInternal.EventActivityIdControl( + (uint)UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, + ref activityId) == 0) #elif PLATFORM_WINDOWS if (UnsafeNativeMethods.ManifestEtw.EventActivityIdControl( UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, ref activityId) == 0) -#endif // FEATURE_PERFTRACING +#endif // FEATURE_PERFTRACING && PLATFORM_WINDOWS { #if FEATURE_ACTIVITYSAMPLING var activityDying = s_activityDying; @@ -97,14 +102,21 @@ namespace System.Diagnostics.Tracing // We ignore errors to keep with the convention that EventSources do not throw errors. // Note we can't access m_throwOnWrites because this is a static method. -#if FEATURE_PERFTRACING - oldActivityThatWillContinue = s_currentThreadActivityId; - s_currentThreadActivityId = activityId; -#elif PLATFORM_WINDOWS +#if FEATURE_PERFTRACING && PLATFORM_WINDOWS + EventPipeInternal.EventActivityIdControl( + (uint)UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID, + ref oldActivityThatWillContinue); +#elif FEATURE_PERFTRACING + EventPipeInternal.EventActivityIdControl( + (uint)UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, + ref oldActivityThatWillContinue); +#endif // FEATURE_PERFTRACING && PLATFORM_WINDOWS + +#if PLATFORM_WINDOWS UnsafeNativeMethods.ManifestEtw.EventActivityIdControl( UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, ref oldActivityThatWillContinue); -#endif // FEATURE_PERFTRACING +#endif // PLATFORM_WINDOWS #endif // FEATURE_MANAGED_ETW // We don't call the activityDying callback here because the caller has declared that @@ -124,13 +136,15 @@ namespace System.Diagnostics.Tracing // errors. Note we can't access m_throwOnWrites because this is a static method. Guid retVal = new Guid(); #if FEATURE_MANAGED_ETW -#if FEATURE_PERFTRACING - retVal = s_currentThreadActivityId; -#elif PLATFORM_WINDOWS +#if PLATFORM_WINDOWS UnsafeNativeMethods.ManifestEtw.EventActivityIdControl( UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID, ref retVal); -#endif // FEATURE_PERFTRACING +#elif FEATURE_PERFTRACING + EventPipeInternal.EventActivityIdControl( + (uint)UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID, + ref retVal); +#endif // PLATFORM_WINDOWS #endif // FEATURE_MANAGED_ETW return retVal; } diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index 8fbfd20..b9706bb 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -1203,6 +1203,7 @@ FCFuncStart(gEventPipeInternalFuncs) QCFuncElement("CreateProvider", EventPipeInternal::CreateProvider) QCFuncElement("DefineEvent", EventPipeInternal::DefineEvent) QCFuncElement("DeleteProvider", EventPipeInternal::DeleteProvider) + QCFuncElement("EventActivityIdControl", EventPipeInternal::EventActivityIdControl) QCFuncElement("WriteEvent", EventPipeInternal::WriteEvent) QCFuncElement("WriteEventData", EventPipeInternal::WriteEventData) FCFuncEnd() diff --git a/src/vm/eventpipe.cpp b/src/vm/eventpipe.cpp index 41af3ef..2d7e302 100644 --- a/src/vm/eventpipe.cpp +++ b/src/vm/eventpipe.cpp @@ -545,6 +545,12 @@ void EventPipe::WriteEventInternal(EventPipeEvent &event, EventPipeEventPayload return; } + // If the activity id isn't specified, pull it from the current thread. + if(pActivityId == NULL) + { + pActivityId = pThread->GetActivityId(); + } + if(!s_pConfig->RundownEnabled() && s_pBufferManager != NULL) { if(!s_pBufferManager->WriteEvent(pThread, *s_pSession, event, payload, pActivityId, pRelatedActivityId)) @@ -964,6 +970,67 @@ void QCALLTYPE EventPipeInternal::DeleteProvider( END_QCALL; } +int QCALLTYPE EventPipeInternal::EventActivityIdControl( + uint controlCode, + GUID *pActivityId) +{ + + QCALL_CONTRACT; + + int retVal = 0; + + BEGIN_QCALL; + + Thread *pThread = GetThread(); + if(pThread == NULL || pActivityId == NULL) + { + retVal = 1; + } + else + { + ActivityControlCode activityControlCode = (ActivityControlCode)controlCode; + GUID currentActivityId; + switch(activityControlCode) + { + case ActivityControlCode::EVENT_ACTIVITY_CONTROL_GET_ID: + + *pActivityId = *pThread->GetActivityId(); + break; + + case ActivityControlCode::EVENT_ACTIVITY_CONTROL_SET_ID: + + pThread->SetActivityId(pActivityId); + break; + + case ActivityControlCode::EVENT_ACTIVITY_CONTROL_CREATE_ID: + + CoCreateGuid(pActivityId); + break; + + case ActivityControlCode::EVENT_ACTIVITY_CONTROL_GET_SET_ID: + + currentActivityId = *pThread->GetActivityId(); + pThread->SetActivityId(pActivityId); + *pActivityId = currentActivityId; + + break; + + case ActivityControlCode::EVENT_ACTIVITY_CONTROL_CREATE_SET_ID: + + *pActivityId = *pThread->GetActivityId(); + CoCreateGuid(¤tActivityId); + pThread->SetActivityId(¤tActivityId); + break; + + default: + retVal = 1; + }; + } + + END_QCALL; + return retVal; +} + void QCALLTYPE EventPipeInternal::WriteEvent( INT_PTR eventHandle, UINT32 eventID, diff --git a/src/vm/eventpipe.h b/src/vm/eventpipe.h index d1f7d60..907e0d7 100644 --- a/src/vm/eventpipe.h +++ b/src/vm/eventpipe.h @@ -356,6 +356,16 @@ public: class EventPipeInternal { +private: + + enum class ActivityControlCode + { + EVENT_ACTIVITY_CONTROL_GET_ID = 1, + EVENT_ACTIVITY_CONTROL_SET_ID = 2, + EVENT_ACTIVITY_CONTROL_CREATE_ID = 3, + EVENT_ACTIVITY_CONTROL_GET_SET_ID = 4, + EVENT_ACTIVITY_CONTROL_CREATE_SET_ID = 5 + }; public: @@ -384,6 +394,10 @@ public: static void QCALLTYPE DeleteProvider( INT_PTR provHandle); + static int QCALLTYPE EventActivityIdControl( + uint controlCode, + GUID *pActivityId); + static void QCALLTYPE WriteEvent( INT_PTR eventHandle, UINT32 eventID, diff --git a/src/vm/threads.cpp b/src/vm/threads.cpp index 5482102..422789b 100644 --- a/src/vm/threads.cpp +++ b/src/vm/threads.cpp @@ -1687,6 +1687,7 @@ Thread::Thread() #ifdef FEATURE_PERFTRACING m_pEventPipeBufferList = NULL; m_eventWriteInProgress = false; + memset(&m_activityId, 0, sizeof(m_activityId)); #endif // FEATURE_PERFTRACING m_HijackReturnKind = RT_Illegal; } diff --git a/src/vm/threads.h b/src/vm/threads.h index 57cb872..d55d1f8 100644 --- a/src/vm/threads.h +++ b/src/vm/threads.h @@ -5256,6 +5256,10 @@ private: // True if the thread was in cooperative mode. False if it was in preemptive when the suspension started. Volatile m_gcModeOnSuspension; + // The activity ID for the current thread. + // An activity ID of zero means the thread is not executing in the context of an activity. + GUID m_activityId; + public: EventPipeBufferList* GetEventPipeBufferList() { @@ -5297,6 +5301,20 @@ public: { m_gcModeOnSuspension = 0; } + + LPCGUID GetActivityId() const + { + LIMITED_METHOD_CONTRACT; + return &m_activityId; + } + + void SetActivityId(LPCGUID pActivityId) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(pActivityId != NULL); + + m_activityId = *pActivityId; + } #endif // FEATURE_PERFTRACING #ifdef FEATURE_HIJACK diff --git a/tests/src/tracing/common/Assert.cs b/tests/src/tracing/common/Assert.cs new file mode 100755 index 0000000..c538ecc --- /dev/null +++ b/tests/src/tracing/common/Assert.cs @@ -0,0 +1,40 @@ +using System; + +namespace Tracing.Tests.Common +{ + public static class Assert + { + public static void Equal(T left, T right) where T : IEquatable + { + if (left == null && right != null) + { + throw new Exception( + string.Format("Values are not equal! Left=NULL Right='{0}'", right)); + } + else if (left != null && right == null) + { + throw new Exception( + string.Format("Values are not equal! Left='{0}' Right=NULL", left)); + } + else if (!left.Equals(right)) + { + throw new Exception( + string.Format("Values are not equal! Left='{0}' Right='{1}'", left, right)); + } + } + + public static void NotEqual(T left, T right) where T : IEquatable + { + if (left == null && right == null) + { + throw new Exception( + "Values are equal! Left=NULL Right=NULL"); + } + else if (left != null && left.Equals(right)) + { + throw new Exception( + string.Format("Values are equal! Left='{0}' Right='{1}'", left, right)); + } + } + } +} diff --git a/tests/src/tracing/common/common.csproj b/tests/src/tracing/common/common.csproj index ca10e7f..7bb5dfc 100644 --- a/tests/src/tracing/common/common.csproj +++ b/tests/src/tracing/common/common.csproj @@ -25,6 +25,7 @@ + diff --git a/tests/src/tracing/eventactivityidcontrol/EventActivityIdControl.cs b/tests/src/tracing/eventactivityidcontrol/EventActivityIdControl.cs new file mode 100755 index 0000000..1a7e288 --- /dev/null +++ b/tests/src/tracing/eventactivityidcontrol/EventActivityIdControl.cs @@ -0,0 +1,221 @@ +using System; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Tracing.Tests.Common; + +namespace Tracing.Tests +{ + public static class EventActivityIdControlTest + { + internal enum ActivityControlCode : uint + { + EVENT_ACTIVITY_CTRL_GET_ID = 1, + EVENT_ACTIVITY_CTRL_SET_ID = 2, + EVENT_ACTIVITY_CTRL_CREATE_ID = 3, + EVENT_ACTIVITY_CTRL_GET_SET_ID = 4, + EVENT_ACTIVITY_CTRL_CREATE_SET_ID = 5 + }; + + private const uint NumThreads = 10; + private const uint NumTasks = 20; + + private static MethodInfo s_EventActivityIdControl; + private static object s_EventPipeEventProvider; + private static bool s_FailureEncountered = false; + + static int Main(string[] args) + { + if(!Initialize()) + { + return -1; + } + + // Run the test on the start-up thread. + TestThreadProc(); + + // Run the test on some background threads. + Thread[] threads = new Thread[NumThreads]; + for(int i=0; i(retCode, 0); + Assert.Equal(activityId, Guid.Empty); + + // Set the activity ID to a random GUID and then confirm that it was properly set. + activityId = Guid.NewGuid(); + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_SET_ID, + ref activityId); + Assert.Equal(retCode, 0); + + Guid currActivityId = Guid.Empty; + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_GET_ID, + ref currActivityId); + Assert.Equal(retCode, 0); + Assert.Equal(currActivityId, activityId); + + // Set and get the activity ID in one call. + activityId = Guid.NewGuid(); + Guid savedActivityId = activityId; + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_GET_SET_ID, + ref activityId); + Assert.Equal(retCode, 0); + Assert.Equal(currActivityId, activityId); + + // Validate that the value we specified in the previous call is what comes back from a call to Get. + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_GET_ID, + ref activityId); + Assert.Equal(retCode, 0); + Assert.Equal(savedActivityId, activityId); + + // Create a new ID but don't change the current value. + Guid newActivityId = Guid.Empty; + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_CREATE_ID, + ref newActivityId); + Assert.Equal(retCode, 0); + Assert.NotEqual(newActivityId, Guid.Empty); + + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_GET_ID, + ref activityId); + Assert.Equal(retCode, 0); + Assert.Equal(savedActivityId, activityId); + + // Create a new ID and set it in one action. + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_CREATE_SET_ID, + ref activityId); + Assert.Equal(retCode, 0); + Assert.Equal(savedActivityId, activityId); + + savedActivityId = activityId; + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_GET_ID, + ref activityId); + Assert.Equal(retCode, 0); + Assert.NotEqual(savedActivityId, activityId); + Assert.NotEqual(activityId, Guid.Empty); + + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_GET_ID, + ref newActivityId); + Assert.Equal(retCode, 0); + Assert.Equal(activityId, newActivityId); + + // Set the activity ID back to zero. + activityId = Guid.Empty; + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_SET_ID, + ref activityId); + Assert.Equal(retCode, 0); + + retCode = EventActivityIdControl( + ActivityControlCode.EVENT_ACTIVITY_CTRL_GET_ID, + ref activityId); + Assert.Equal(retCode, 0); + Assert.Equal(activityId, Guid.Empty); + + // Try pass an invalid control code. + activityId = Guid.NewGuid(); + savedActivityId = activityId; + retCode = EventActivityIdControl( + (ActivityControlCode)10, + ref activityId); + Assert.Equal(retCode, 1); + Assert.Equal(activityId, savedActivityId); + } + catch(Exception ex) + { + s_FailureEncountered = true; + Console.WriteLine(ex.ToString()); + } + } + + private static int EventActivityIdControl( + ActivityControlCode controlCode, + ref Guid activityId) + { + object[] parameters = new object[] + { + (uint)controlCode, + activityId + }; + + int retCode = (int) s_EventActivityIdControl.Invoke( + s_EventPipeEventProvider, + parameters); + + // Copy the by ref activityid out of the parameters array. + activityId = (Guid)parameters[1]; + return retCode; + } + + private static bool Initialize() + { + // Reflect over System.Private.CoreLib and get the EventPipeEventProvider type and EventActivityIdControl method. + Assembly SPC = typeof(System.Diagnostics.Tracing.EventSource).Assembly; + if(SPC == null) + { + Console.WriteLine("Failed to get System.Private.CoreLib assembly."); + return false; + } + Type eventPipeEventProviderType = SPC.GetType("System.Diagnostics.Tracing.EventPipeEventProvider"); + if(eventPipeEventProviderType == null) + { + Console.WriteLine("Failed to get System.Diagnostics.Tracing.EventPipeEventProvider type."); + return false; + } + s_EventActivityIdControl = eventPipeEventProviderType.GetMethod("System.Diagnostics.Tracing.IEventProvider.EventActivityIdControl", BindingFlags.NonPublic | BindingFlags.Instance ); + if(s_EventActivityIdControl == null) + { + Console.WriteLine("Failed to get EventActivityIdControl method."); + return false; + } + + // Create an instance of EventPipeEventProvider. + s_EventPipeEventProvider = Activator.CreateInstance(eventPipeEventProviderType); + if(s_EventPipeEventProvider == null) + { + Console.WriteLine("Failed to create EventPipeEventProvider instance."); + } + + return true; + } + } +} diff --git a/tests/src/tracing/eventactivityidcontrol/eventactivityidcontrol.csproj b/tests/src/tracing/eventactivityidcontrol/eventactivityidcontrol.csproj new file mode 100644 index 0000000..9ec1be2 --- /dev/null +++ b/tests/src/tracing/eventactivityidcontrol/eventactivityidcontrol.csproj @@ -0,0 +1,30 @@ + + + + + Debug + AnyCPU + 2.0 + {8E3244CB-407F-4142-BAAB-E7A55901A5FA} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + BuildAndRun + $(DefineConstants);STATIC + true + 0 + + + + + + + False + + + + + + + + -- 2.7.4