From: Brian Robbins Date: Fri, 12 May 2017 17:51:31 +0000 (-0700) Subject: EventPipe Circular Buffer Support and Ability to Start/Stop Tracing (#11507) X-Git-Tag: accepted/tizen/base/20180629.140029~1109^2~47 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=fca97d0ca72524b3bdd61817f7a172dd47d53287;p=platform%2Fupstream%2Fcoreclr.git EventPipe Circular Buffer Support and Ability to Start/Stop Tracing (#11507) --- diff --git a/src/mscorlib/System.Private.CoreLib.csproj b/src/mscorlib/System.Private.CoreLib.csproj index 3373097..75d5ab8 100644 --- a/src/mscorlib/System.Private.CoreLib.csproj +++ b/src/mscorlib/System.Private.CoreLib.csproj @@ -569,6 +569,7 @@ + @@ -734,4 +735,4 @@ $(IntermediateOutputPath)\System.Private.CoreLib.res - \ No newline at end of file + diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipe.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipe.cs new file mode 100644 index 0000000..4c6778f --- /dev/null +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipe.cs @@ -0,0 +1,157 @@ +// 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.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using Microsoft.Win32; + +namespace System.Diagnostics.Tracing +{ + [StructLayout(LayoutKind.Sequential)] + internal struct EventPipeProviderConfiguration + { + [MarshalAs(UnmanagedType.LPWStr)] + private string m_providerName; + private UInt64 m_keywords; + private uint m_loggingLevel; + + internal EventPipeProviderConfiguration( + string providerName, + UInt64 keywords, + uint loggingLevel) + { + if(string.IsNullOrEmpty(providerName)) + { + throw new ArgumentNullException(nameof(providerName)); + } + if(loggingLevel > 5) // 5 == Verbose, the highest value in EventPipeLoggingLevel. + { + throw new ArgumentOutOfRangeException(nameof(loggingLevel)); + } + m_providerName = providerName; + m_keywords = keywords; + m_loggingLevel = loggingLevel; + } + + internal string ProviderName + { + get { return m_providerName; } + } + + internal UInt64 Keywords + { + get { return m_keywords; } + } + + internal uint LoggingLevel + { + get { return m_loggingLevel; } + } + } + + internal sealed class EventPipeConfiguration + { + private string m_outputFile; + private uint m_circularBufferSizeInMB; + private List m_providers; + + internal EventPipeConfiguration( + string outputFile, + uint circularBufferSizeInMB) + { + if(string.IsNullOrEmpty(outputFile)) + { + throw new ArgumentNullException(nameof(outputFile)); + } + if(circularBufferSizeInMB == 0) + { + throw new ArgumentOutOfRangeException(nameof(circularBufferSizeInMB)); + } + m_outputFile = outputFile; + m_circularBufferSizeInMB = circularBufferSizeInMB; + m_providers = new List(); + } + + internal string OutputFile + { + get { return m_outputFile; } + } + + internal uint CircularBufferSizeInMB + { + get { return m_circularBufferSizeInMB; } + } + + internal EventPipeProviderConfiguration[] Providers + { + get { return m_providers.ToArray(); } + } + + internal void EnableProvider(string providerName, UInt64 keywords, uint loggingLevel) + { + m_providers.Add(new EventPipeProviderConfiguration( + providerName, + keywords, + loggingLevel)); + } + } + + internal static class EventPipe + { + internal static void Enable(EventPipeConfiguration configuration) + { + if(configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + EventPipeProviderConfiguration[] providers = configuration.Providers; + + EventPipeInternal.Enable( + configuration.OutputFile, + configuration.CircularBufferSizeInMB, + providers, + providers.Length); + } + + internal static void Disable() + { + EventPipeInternal.Disable(); + } + } + + internal static class EventPipeInternal + { + // + // These PInvokes are used by the configuration APIs to interact with EventPipe. + // + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + internal static extern void Enable(string outputFile, uint circularBufferSizeInMB, EventPipeProviderConfiguration[] providers, int numProviders); + + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + internal static extern void Disable(); + + // + // These PInvokes are used by EventSource to interact with the EventPipe. + // + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + internal static extern IntPtr CreateProvider(Guid providerID, UnsafeNativeMethods.ManifestEtw.EtwEnableCallback callbackFunc); + + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + internal static extern IntPtr AddEvent(IntPtr provHandle, Int64 keywords, uint eventID, uint eventVersion, uint level, bool needStack); + + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + internal static extern void DeleteProvider(IntPtr provHandle); + + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + internal static extern unsafe void WriteEvent(IntPtr eventHandle, void* data, uint length); + } +} diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs index 5917ecc..42eb42a 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs @@ -63,24 +63,4 @@ namespace System.Diagnostics.Tracing return 0; } } - - // PInvokes into the runtime used to interact with the EventPipe. - internal static class EventPipeInternal - { - [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] - [SuppressUnmanagedCodeSecurity] - internal static extern IntPtr CreateProvider(Guid providerID, UnsafeNativeMethods.ManifestEtw.EtwEnableCallback callbackFunc); - - [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] - [SuppressUnmanagedCodeSecurity] - internal static extern IntPtr AddEvent(IntPtr provHandle, Int64 keywords, uint eventID, uint eventVersion, uint level, bool needStack); - - [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] - [SuppressUnmanagedCodeSecurity] - internal static extern void DeleteProvider(IntPtr provHandle); - - [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] - [SuppressUnmanagedCodeSecurity] - internal static extern unsafe void WriteEvent(IntPtr eventHandle, void* data, uint length); - } } diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 556eb0e..c610d3c 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -170,6 +170,8 @@ set(VM_SOURCES_WKS eventpipefile.cpp eventpipejsonfile.cpp eventpipeprovider.cpp + eventpipebuffer.cpp + eventpipebuffermanager.cpp eventstore.cpp fastserializer.cpp fcall.cpp diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index ef82daa..c51b4a8 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -1273,6 +1273,8 @@ FCFuncEnd() #ifdef FEATURE_PERFTRACING FCFuncStart(gEventPipeInternalFuncs) + QCFuncElement("Enable", EventPipeInternal::Enable) + QCFuncElement("Disable", EventPipeInternal::Disable) QCFuncElement("CreateProvider", EventPipeInternal::CreateProvider) QCFuncElement("AddEvent", EventPipeInternal::AddEvent) QCFuncElement("DeleteProvider", EventPipeInternal::DeleteProvider) diff --git a/src/vm/eventpipe.cpp b/src/vm/eventpipe.cpp index bed4cfd..5660a56 100644 --- a/src/vm/eventpipe.cpp +++ b/src/vm/eventpipe.cpp @@ -4,6 +4,7 @@ #include "common.h" #include "eventpipe.h" +#include "eventpipebuffermanager.h" #include "eventpipeconfiguration.h" #include "eventpipeevent.h" #include "eventpipefile.h" @@ -20,8 +21,17 @@ CrstStatic EventPipe::s_configCrst; bool EventPipe::s_tracingInitialized = false; EventPipeConfiguration* EventPipe::s_pConfig = NULL; +EventPipeBufferManager* EventPipe::s_pBufferManager = NULL; EventPipeFile* EventPipe::s_pFile = NULL; +#ifdef _DEBUG +EventPipeFile* EventPipe::s_pSyncFile = NULL; EventPipeJsonFile* EventPipe::s_pJsonFile = NULL; +#endif // _DEBUG + +#ifdef FEATURE_PAL +// This function is auto-generated from /src/scripts/genEventPipe.py +extern "C" void InitProvidersAndEvents(); +#endif #ifdef FEATURE_PAL // This function is auto-generated from /src/scripts/genEventPipe.py @@ -39,6 +49,8 @@ void EventPipe::Initialize() s_pConfig = new EventPipeConfiguration(); s_pConfig->Initialize(); + s_pBufferManager = new EventPipeBufferManager(); + #ifdef FEATURE_PAL // This calls into auto-generated code to initialize the runtime providers // and events so that the EventPipe configuration lock isn't taken at runtime @@ -57,9 +69,15 @@ void EventPipe::EnableOnStartup() CONTRACTL_END; // Test COMPLUS variable to enable tracing at start-up. - if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PerformanceTracing) != 0) + if((CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PerformanceTracing) & 1) == 1) { - Enable(); + SString outputPath; + outputPath.Printf("Process-%d.netperf", GetCurrentProcessId()); + Enable( + outputPath.GetUnicode(), + 1024 /* 1 GB circular buffer */, + NULL /* pProviders */, + 0 /* numProviders */); } } @@ -74,9 +92,24 @@ void EventPipe::Shutdown() CONTRACTL_END; Disable(); + + if(s_pConfig != NULL) + { + delete(s_pConfig); + s_pConfig = NULL; + } + if(s_pBufferManager != NULL) + { + delete(s_pBufferManager); + s_pBufferManager = NULL; + } } -void EventPipe::Enable() +void EventPipe::Enable( + LPCWSTR strOutputPath, + uint circularBufferSizeInMB, + EventPipeProviderConfiguration *pProviders, + int numProviders) { CONTRACTL { @@ -86,7 +119,8 @@ void EventPipe::Enable() } CONTRACTL_END; - if(!s_tracingInitialized) + // If tracing is not initialized or is already enabled, bail here. + if(!s_tracingInitialized || s_pConfig->Enabled()) { return; } @@ -95,26 +129,29 @@ void EventPipe::Enable() CrstHolder _crst(GetLock()); // Create the event pipe file. - SString eventPipeFileOutputPath; - eventPipeFileOutputPath.Printf("Process-%d.netperf", GetCurrentProcessId()); + SString eventPipeFileOutputPath(strOutputPath); s_pFile = new EventPipeFile(eventPipeFileOutputPath); - if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PerformanceTracing) == 2) +#ifdef _DEBUG + if((CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PerformanceTracing) & 2) == 2) { - // File placed in current working directory. + // Create a synchronous file. + SString eventPipeSyncFileOutputPath; + eventPipeSyncFileOutputPath.Printf("Process-%d.sync.netperf", GetCurrentProcessId()); + s_pSyncFile = new EventPipeFile(eventPipeSyncFileOutputPath); + + // Create a JSON file. SString outputFilePath; outputFilePath.Printf("Process-%d.PerfView.json", GetCurrentProcessId()); s_pJsonFile = new EventPipeJsonFile(outputFilePath); } +#endif // _DEBUG // Enable tracing. - s_pConfig->Enable(); + s_pConfig->Enable(circularBufferSizeInMB, pProviders, numProviders); // Enable the sample profiler SampleProfiler::Enable(); - - // TODO: Iterate through the set of providers, enable them as appropriate. - // This in-turn will iterate through all of the events and set their isEnabled bits. } void EventPipe::Disable() @@ -133,28 +170,41 @@ void EventPipe::Disable() // Take the lock before disabling tracing. CrstHolder _crst(GetLock()); - // Disable the profiler. - SampleProfiler::Disable(); + if(s_pConfig->Enabled()) + { + // Disable the profiler. + SampleProfiler::Disable(); - // Disable tracing. - s_pConfig->Disable(); + // Disable tracing. + s_pConfig->Disable(); - if(s_pJsonFile != NULL) - { - delete(s_pJsonFile); - s_pJsonFile = NULL; - } + // Flush all write buffers to make sure that all threads see the change. + FlushProcessWriteBuffers(); - if(s_pFile != NULL) - { - delete(s_pFile); - s_pFile = NULL; - } + // Write to the file. + LARGE_INTEGER disableTimeStamp; + QueryPerformanceCounter(&disableTimeStamp); + s_pBufferManager->WriteAllBuffersToFile(s_pFile, disableTimeStamp); + if(s_pFile != NULL) + { + delete(s_pFile); + s_pFile = NULL; + } +#ifdef _DEBUG + if(s_pSyncFile != NULL) + { + delete(s_pSyncFile); + s_pSyncFile = NULL; + } + if(s_pJsonFile != NULL) + { + delete(s_pJsonFile); + s_pJsonFile = NULL; + } +#endif // _DEBUG - if(s_pConfig != NULL) - { - delete(s_pConfig); - s_pConfig = NULL; + // De-allocate buffers. + s_pBufferManager->DeAllocateBuffers(); } } @@ -165,6 +215,7 @@ void EventPipe::WriteEvent(EventPipeEvent &event, BYTE *pData, unsigned int leng NOTHROW; GC_NOTRIGGER; MODE_ANY; + PRECONDITION(s_pBufferManager != NULL); } CONTRACTL_END; @@ -174,29 +225,50 @@ void EventPipe::WriteEvent(EventPipeEvent &event, BYTE *pData, unsigned int leng return; } - DWORD threadID = GetCurrentThreadId(); - - // Create an instance of the event. - EventPipeEventInstance instance( - event, - threadID, - pData, - length); + // Get the current thread; + Thread *pThread = GetThread(); + if(pThread == NULL) + { + // We can't write an event without the thread object. + return; + } - // Write to the EventPipeFile. - if(s_pFile != NULL) + if(s_pBufferManager != NULL) { - s_pFile->WriteEvent(instance); + if(!s_pBufferManager->WriteEvent(pThread, event, pData, length)) + { + // This is used in DEBUG to make sure that we don't log an event synchronously that we didn't log to the buffer. + return; + } } - // Write to the EventPipeJsonFile if it exists. - if(s_pJsonFile != NULL) +#ifdef _DEBUG { - s_pJsonFile->WriteEvent(instance); + GCX_PREEMP(); + + // Create an instance of the event for the synchronous path. + EventPipeEventInstance instance( + event, + pThread->GetOSThreadId(), + pData, + length); + + // Write to the EventPipeFile if it exists. + if(s_pSyncFile != NULL) + { + s_pSyncFile->WriteEvent(instance); + } + + // Write to the EventPipeJsonFile if it exists. + if(s_pJsonFile != NULL) + { + s_pJsonFile->WriteEvent(instance); + } } +#endif // _DEBUG } -void EventPipe::WriteSampleProfileEvent(SampleProfilerEventInstance &instance) +void EventPipe::WriteSampleProfileEvent(Thread *pSamplingThread, Thread *pTargetThread, StackContents &stackContents) { CONTRACTL { @@ -206,17 +278,39 @@ void EventPipe::WriteSampleProfileEvent(SampleProfilerEventInstance &instance) } CONTRACTL_END; - // Write to the EventPipeFile. - if(s_pFile != NULL) + // Write the event to the thread's buffer. + if(s_pBufferManager != NULL) { - s_pFile->WriteEvent(instance); + // Specify the sampling thread as the "current thread", so that we select the right buffer. + // Specify the target thread so that the event gets properly attributed. + if(!s_pBufferManager->WriteEvent(pSamplingThread, *SampleProfiler::s_pThreadTimeEvent, NULL, 0, pTargetThread, &stackContents)) + { + // This is used in DEBUG to make sure that we don't log an event synchronously that we didn't log to the buffer. + return; + } } - // Write to the EventPipeJsonFile if it exists. - if(s_pJsonFile != NULL) +#ifdef _DEBUG { - s_pJsonFile->WriteEvent(instance); + GCX_PREEMP(); + + // Create an instance for the synchronous path. + SampleProfilerEventInstance instance(pTargetThread); + stackContents.CopyTo(instance.GetStack()); + + // Write to the EventPipeFile. + if(s_pSyncFile != NULL) + { + s_pSyncFile->WriteEvent(instance); + } + + // Write to the EventPipeJsonFile if it exists. + if(s_pJsonFile != NULL) + { + s_pJsonFile->WriteEvent(instance); + } } +#endif // _DEBUG } bool EventPipe::WalkManagedStackForCurrentThread(StackContents &stackContents) @@ -308,6 +402,28 @@ CrstStatic* EventPipe::GetLock() return &s_configCrst; } +void QCALLTYPE EventPipeInternal::Enable( + __in_z LPCWSTR outputFile, + unsigned int circularBufferSizeInMB, + EventPipeProviderConfiguration *pProviders, + int numProviders) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + EventPipe::Enable(outputFile, circularBufferSizeInMB, pProviders, numProviders); + END_QCALL; +} + +void QCALLTYPE EventPipeInternal::Disable() +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + EventPipe::Disable(); + END_QCALL; +} + INT_PTR QCALLTYPE EventPipeInternal::CreateProvider( GUID providerID, EventPipeCallback pCallbackFunc) diff --git a/src/vm/eventpipe.h b/src/vm/eventpipe.h index d5b85f7..626c4df 100644 --- a/src/vm/eventpipe.h +++ b/src/vm/eventpipe.h @@ -15,6 +15,8 @@ class EventPipeConfiguration; class EventPipeEvent; class EventPipeFile; class EventPipeJsonFile; +class EventPipeBuffer; +class EventPipeBufferManager; class MethodDesc; class SampleProfilerEventInstance; @@ -28,9 +30,11 @@ private: // Top of stack is at index 0. UINT_PTR m_stackFrames[MAX_STACK_DEPTH]; +#ifdef _DEBUG // Parallel array of MethodDesc pointers. // Used for debug-only stack printing. MethodDesc* m_methods[MAX_STACK_DEPTH]; +#endif // _DEBUG // The next available slot in StackFrames. unsigned int m_nextAvailableFrame; @@ -44,6 +48,18 @@ public: Reset(); } + void CopyTo(StackContents *pDest) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(pDest != NULL); + + memcpy_s(pDest->m_stackFrames, MAX_STACK_DEPTH * sizeof(UINT_PTR), m_stackFrames, sizeof(UINT_PTR) * m_nextAvailableFrame); +#ifdef _DEBUG + memcpy_s(pDest->m_methods, MAX_STACK_DEPTH * sizeof(MethodDesc*), m_methods, sizeof(MethodDesc*) * m_nextAvailableFrame); +#endif + pDest->m_nextAvailableFrame = m_nextAvailableFrame; + } + void Reset() { LIMITED_METHOD_CONTRACT; @@ -78,6 +94,7 @@ public: return m_stackFrames[frameIndex]; } +#ifdef _DEBUG MethodDesc* GetMethod(unsigned int frameIndex) { LIMITED_METHOD_CONTRACT; @@ -90,6 +107,7 @@ public: return m_methods[frameIndex]; } +#endif // _DEBUG void Append(UINT_PTR controlPC, MethodDesc *pMethod) { @@ -98,7 +116,9 @@ public: if(m_nextAvailableFrame < MAX_STACK_DEPTH) { m_stackFrames[m_nextAvailableFrame] = controlPC; +#ifdef _DEBUG m_methods[m_nextAvailableFrame] = pMethod; +#endif m_nextAvailableFrame++; } } @@ -124,6 +144,7 @@ class EventPipe friend class EventPipeConfiguration; friend class EventPipeFile; friend class EventPipeProvider; + friend class EventPipeBufferManager; friend class SampleProfiler; public: @@ -138,7 +159,11 @@ class EventPipe static void EnableOnStartup(); // Enable tracing via the event pipe. - static void Enable(); + static void Enable( + LPCWSTR strOutputPath, + uint circularBufferSizeInMB, + EventPipeProviderConfiguration *pProviders, + int numProviders); // Disable tracing via the event pipe. static void Disable(); @@ -148,7 +173,7 @@ class EventPipe static void WriteEvent(EventPipeEvent &event, BYTE *pData, unsigned int length); // Write out a sample profile event. - static void WriteSampleProfileEvent(SampleProfilerEventInstance &instance); + static void WriteSampleProfileEvent(Thread *pSamplingThread, Thread *pTargetThread, StackContents &stackContents); // Get the managed call stack for the current thread. static bool WalkManagedStackForCurrentThread(StackContents &stackContents); @@ -171,8 +196,42 @@ class EventPipe static CrstStatic s_configCrst; static bool s_tracingInitialized; static EventPipeConfiguration *s_pConfig; + static EventPipeBufferManager *s_pBufferManager; static EventPipeFile *s_pFile; +#ifdef _DEBUG + static EventPipeFile *s_pSyncFile; static EventPipeJsonFile *s_pJsonFile; +#endif // _DEBUG +}; + +struct EventPipeProviderConfiguration +{ + +private: + + LPCWSTR m_pProviderName; + UINT64 m_keywords; + unsigned int m_loggingLevel; + +public: + + LPCWSTR GetProviderName() const + { + LIMITED_METHOD_CONTRACT; + return m_pProviderName; + } + + UINT64 GetKeywords() const + { + LIMITED_METHOD_CONTRACT; + return m_keywords; + } + + unsigned int GetLevel() const + { + LIMITED_METHOD_CONTRACT; + return m_loggingLevel; + } }; class EventPipeInternal @@ -180,6 +239,14 @@ class EventPipeInternal public: + static void QCALLTYPE Enable( + __in_z LPCWSTR outputFile, + unsigned int circularBufferSizeInMB, + EventPipeProviderConfiguration *pProviders, + int numProviders); + + static void QCALLTYPE Disable(); + static INT_PTR QCALLTYPE CreateProvider( GUID providerID, EventPipeCallback pCallbackFunc); diff --git a/src/vm/eventpipebuffer.cpp b/src/vm/eventpipebuffer.cpp new file mode 100644 index 0000000..e0e9610 --- /dev/null +++ b/src/vm/eventpipebuffer.cpp @@ -0,0 +1,275 @@ +// 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. + + +#include "common.h" +#include "eventpipeeventinstance.h" +#include "eventpipebuffer.h" + +#ifdef FEATURE_PERFTRACING + +EventPipeBuffer::EventPipeBuffer(unsigned int bufferSize) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + m_pBuffer = new BYTE[bufferSize]; + memset(m_pBuffer, 0, bufferSize); + m_pCurrent = m_pBuffer; + m_pLimit = m_pBuffer + bufferSize; + + m_mostRecentTimeStamp.QuadPart = 0; + m_pLastPoppedEvent = NULL; + m_pPrevBuffer = NULL; + m_pNextBuffer = NULL; +} + +EventPipeBuffer::~EventPipeBuffer() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if(m_pBuffer != NULL) + { + delete[] m_pBuffer; + } +} + +bool EventPipeBuffer::WriteEvent(Thread *pThread, EventPipeEvent &event, BYTE *pData, unsigned int dataLength, StackContents *pStack) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pThread != NULL); + } + CONTRACTL_END; + + // Calculate the size of the event. + unsigned int eventSize = sizeof(EventPipeEventInstance) + dataLength; + + // Make sure we have enough space to write the event. + if(m_pCurrent + eventSize >= m_pLimit) + { + return false; + } + + // Calculate the location of the data payload. + BYTE *pDataDest = m_pCurrent + sizeof(EventPipeEventInstance); + + bool success = true; + EX_TRY + { + // Placement-new the EventPipeEventInstance. + EventPipeEventInstance *pInstance = new (m_pCurrent) EventPipeEventInstance( + event, + pThread->GetOSThreadId(), + pDataDest, + dataLength); + + // Copy the stack if a separate stack trace was provided. + if(pStack != NULL) + { + StackContents *pInstanceStack = pInstance->GetStack(); + pStack->CopyTo(pInstanceStack); + } + + // Write the event payload data to the buffer. + if(dataLength > 0) + { + memcpy(pDataDest, pData, dataLength); + } + + // Save the most recent event timestamp. + m_mostRecentTimeStamp = pInstance->GetTimeStamp(); + + } + EX_CATCH + { + // If a failure occurs, bail out and don't advance the pointer. + success = false; + } + EX_END_CATCH(SwallowAllExceptions); + + if(success) + { + // Advance the current pointer past the event. + m_pCurrent += eventSize; + } + + return success; +} + +LARGE_INTEGER EventPipeBuffer::GetMostRecentTimeStamp() const +{ + LIMITED_METHOD_CONTRACT; + + return m_mostRecentTimeStamp; +} + +void EventPipeBuffer::Clear() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + memset(m_pBuffer, 0, (size_t)(m_pLimit - m_pBuffer)); + m_pCurrent = m_pBuffer; + m_mostRecentTimeStamp.QuadPart = 0; + m_pLastPoppedEvent = NULL; +} + +EventPipeEventInstance* EventPipeBuffer::GetNext(EventPipeEventInstance *pEvent, LARGE_INTEGER beforeTimeStamp) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + EventPipeEventInstance *pNextInstance = NULL; + // If input is NULL, return the first event if there is one. + if(pEvent == NULL) + { + // If this buffer contains an event, select it. + if(m_pCurrent > m_pBuffer) + { + pNextInstance = (EventPipeEventInstance*)m_pBuffer; + } + } + else + { + // Confirm that pEvent is within the used range of the buffer. + if(((BYTE*)pEvent < m_pBuffer) || ((BYTE*)pEvent >= m_pCurrent)) + { + _ASSERT(!"Input pointer is out of range."); + return NULL; + } + + // We have a pointer within the bounds of the buffer. + // Find the next event by skipping the current event with it's data payload immediately after the instance. + pNextInstance = (EventPipeEventInstance *)(pEvent->GetData() + pEvent->GetLength()); + + // Check to see if we've reached the end of the written portion of the buffer. + if((BYTE*)pNextInstance >= m_pCurrent) + { + return NULL; + } + } + + // Ensure that the timestamp is valid. The buffer is zero'd before use, so a zero timestamp is invalid. + LARGE_INTEGER nextTimeStamp = pNextInstance->GetTimeStamp(); + if(nextTimeStamp.QuadPart == 0) + { + return NULL; + } + + // Ensure that the timestamp is earlier than the beforeTimeStamp. + if(nextTimeStamp.QuadPart >= beforeTimeStamp.QuadPart) + { + return NULL; + } + + return pNextInstance; +} + +EventPipeEventInstance* EventPipeBuffer::PeekNext(LARGE_INTEGER beforeTimeStamp) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Get the next event using the last popped event as a marker. + return GetNext(m_pLastPoppedEvent, beforeTimeStamp); +} + +EventPipeEventInstance* EventPipeBuffer::PopNext(LARGE_INTEGER beforeTimeStamp) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Get the next event using the last popped event as a marker. + EventPipeEventInstance *pNext = PeekNext(beforeTimeStamp); + if(pNext != NULL) + { + m_pLastPoppedEvent = pNext; + } + + return pNext; +} + +#ifdef _DEBUG +bool EventPipeBuffer::EnsureConsistency() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Check to see if the buffer is empty. + if(m_pBuffer == m_pCurrent) + { + // Make sure that the buffer size is greater than zero. + _ASSERTE(m_pBuffer != m_pLimit); + } + + // Validate the contents of the filled portion of the buffer. + BYTE *ptr = m_pBuffer; + while(ptr < m_pCurrent) + { + // Validate the event. + EventPipeEventInstance *pInstance = (EventPipeEventInstance*)ptr; + _ASSERTE(pInstance->EnsureConsistency()); + + // Validate that payload and length match. + _ASSERTE((pInstance->GetData() != NULL && pInstance->GetLength() > 0) || (pInstance->GetData() != NULL && pInstance->GetLength() == 0)); + + // Skip the event. + ptr += sizeof(*pInstance) + pInstance->GetLength(); + } + + // When we're done walking the filled portion of the buffer, + // ptr should be the same as m_pCurrent. + _ASSERTE(ptr == m_pCurrent); + + // Walk the rest of the buffer, making sure it is properly zeroed. + while(ptr < m_pLimit) + { + _ASSERTE(*ptr++ == 0); + } + + return true; +} +#endif // _DEBUG + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipebuffer.h b/src/vm/eventpipebuffer.h new file mode 100644 index 0000000..97b858d --- /dev/null +++ b/src/vm/eventpipebuffer.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef __EVENTPIPE_BUFFER_H__ +#define __EVENTPIPE_BUFFER_H__ + +#ifdef FEATURE_PERFTRACING + +#include "eventpipeevent.h" +#include "eventpipeeventinstance.h" + +class EventPipeBuffer +{ + + friend class EventPipeBufferList; + friend class EventPipeBufferManager; + +private: + + // A pointer to the actual buffer. + BYTE *m_pBuffer; + + // The current write pointer. + BYTE *m_pCurrent; + + // The max write pointer (end of the buffer). + BYTE *m_pLimit; + + // The timestamp of the most recent event in the buffer. + LARGE_INTEGER m_mostRecentTimeStamp; + + // Used by PopNext as input to GetNext. + // If NULL, no events have been popped. + // The event will still remain in the buffer after it is popped, but PopNext will not return it again. + EventPipeEventInstance *m_pLastPoppedEvent; + + // Each buffer will become part of a per-thread linked list of buffers. + // The linked list is invasive, thus we declare the pointers here. + EventPipeBuffer *m_pPrevBuffer; + EventPipeBuffer *m_pNextBuffer; + + unsigned int GetSize() const + { + LIMITED_METHOD_CONTRACT; + return (unsigned int)(m_pLimit - m_pBuffer); + } + + EventPipeBuffer* GetPrevious() const + { + LIMITED_METHOD_CONTRACT; + return m_pPrevBuffer; + } + + EventPipeBuffer* GetNext() const + { + LIMITED_METHOD_CONTRACT; + return m_pNextBuffer; + } + + void SetPrevious(EventPipeBuffer *pBuffer) + { + LIMITED_METHOD_CONTRACT; + m_pPrevBuffer = pBuffer; + } + + void SetNext(EventPipeBuffer *pBuffer) + { + LIMITED_METHOD_CONTRACT; + m_pNextBuffer = pBuffer; + } + +public: + + EventPipeBuffer(unsigned int bufferSize); + ~EventPipeBuffer(); + + // Write an event to the buffer. + // An optional stack trace can be provided for sample profiler events. + // Otherwise, if a stack trace is needed, one will be automatically collected. + // Returns: + // - true: The write succeeded. + // - false: The write failed. In this case, the buffer should be considered full. + bool WriteEvent(Thread *pThread, EventPipeEvent &event, BYTE *pData, unsigned int dataLength, StackContents *pStack = NULL); + + // Get the timestamp of the most recent event in the buffer. + LARGE_INTEGER GetMostRecentTimeStamp() const; + + // Clear the buffer. + void Clear(); + + // Get the next event from the buffer as long as it is before the specified timestamp. + // Input of NULL gets the first event. + EventPipeEventInstance* GetNext(EventPipeEventInstance *pEvent, LARGE_INTEGER beforeTimeStamp); + + // Get the next event from the buffer, but don't mark it read. + EventPipeEventInstance* PeekNext(LARGE_INTEGER beforeTimeStamp); + + // Get the next event from the buffer and mark it as read. + EventPipeEventInstance* PopNext(LARGE_INTEGER beforeTimeStamp); + +#ifdef _DEBUG + bool EnsureConsistency(); +#endif // _DEBUG +}; + +#endif // FEATURE_PERFTRACING + +#endif // __EVENTPIPE_BUFFER_H__ diff --git a/src/vm/eventpipebuffermanager.cpp b/src/vm/eventpipebuffermanager.cpp new file mode 100644 index 0000000..4e6b6b9 --- /dev/null +++ b/src/vm/eventpipebuffermanager.cpp @@ -0,0 +1,798 @@ +// 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. + +#include "common.h" +#include "eventpipeconfiguration.h" +#include "eventpipebuffer.h" +#include "eventpipebuffermanager.h" + +#ifdef FEATURE_PERFTRACING + +EventPipeBufferManager::EventPipeBufferManager() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + m_pPerThreadBufferList = new SList>(); + m_sizeOfAllBuffers = 0; + m_lock.Init(LOCK_TYPE_DEFAULT); + +#ifdef _DEBUG + m_numBuffersAllocated = 0; + m_numBuffersStolen = 0; + m_numBuffersLeaked = 0; + m_numEventsStored = 0; + m_numEventsWritten = 0; +#endif // _DEBUG +} + +EventPipeBuffer* EventPipeBufferManager::AllocateBufferForThread(Thread *pThread, unsigned int requestSize) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pThread != NULL); + PRECONDITION(requestSize > 0); + } + CONTRACTL_END; + + // Allocating a buffer requires us to take the lock. + SpinLockHolder _slh(&m_lock); + + // Determine if the requesting thread has at least one buffer. + // If not, we guarantee that each thread gets at least one (to prevent thrashing when the circular buffer size is too small). + bool allocateNewBuffer = false; + EventPipeBufferList *pThreadBufferList = pThread->GetEventPipeBufferList(); + if(pThreadBufferList == NULL) + { + pThreadBufferList = new EventPipeBufferList(this); + m_pPerThreadBufferList->InsertTail(new SListElem(pThreadBufferList)); + pThread->SetEventPipeBufferList(pThreadBufferList); + allocateNewBuffer = true; + } + + // Determine if policy allows us to allocate another buffer, or if we need to steal one + // from another thread. + if(!allocateNewBuffer) + { + EventPipeConfiguration *pConfig = EventPipe::GetConfiguration(); + if(pConfig == NULL) + { + return NULL; + } + + size_t circularBufferSizeInBytes = pConfig->GetCircularBufferSize(); + if(m_sizeOfAllBuffers < circularBufferSizeInBytes) + { + // We don't worry about the fact that a new buffer could put us over the circular buffer size. + // This is OK, and we won't do it again if we actually go over. + allocateNewBuffer = true; + } + } + + EventPipeBuffer *pNewBuffer = NULL; + if(!allocateNewBuffer) + { + // We can't allocate a new buffer. + // Find the oldest buffer, zero 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(); + if(pListToStealFrom != NULL) + { + // Assert that the buffer we're stealing is not the only buffer in the list. + // This invariant is enforced by FindThreadToStealFrom. + _ASSERTE((pListToStealFrom->GetHead() != NULL) && (pListToStealFrom->GetHead()->GetNext() != NULL)); + + // Remove the oldest buffer from the list. + pNewBuffer = pListToStealFrom->GetAndRemoveHead(); + + // De-allocate the buffer. We do this because buffers are variable sized + // based on how much volume is coming from the thread. + DeAllocateBuffer(pNewBuffer); + pNewBuffer = NULL; + + // Set that we want to allocate a new buffer. + allocateNewBuffer = true; + +#ifdef _DEBUG + m_numBuffersStolen++; +#endif // _DEBUG + + } + else + { + // This only happens when # of threads == # of buffers. + // We'll allocate one more buffer, and then this won't happen again. + allocateNewBuffer = true; + } + } + + if(allocateNewBuffer) + { + // Pick a buffer size by multiplying the base buffer size by the number of buffers already allocated for this thread. + unsigned int sizeMultiplier = pThreadBufferList->GetCount() + 1; + + // 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 +#else + 100 * 1024; // 100K +#endif + unsigned int bufferSize = baseBufferSize * sizeMultiplier; + + // Make sure that buffer size >= request size so that the buffer size does not + // determine the max event size. + if(bufferSize < requestSize) + { + bufferSize = requestSize; + } + + pNewBuffer = new EventPipeBuffer(bufferSize); + m_sizeOfAllBuffers += bufferSize; +#ifdef _DEBUG + m_numBuffersAllocated++; +#endif // _DEBUG + } + + // Set the buffer on the thread. + if(pNewBuffer != NULL) + { + pThreadBufferList->InsertTail(pNewBuffer); + return pNewBuffer; + } + + return NULL; +} + +EventPipeBufferList* EventPipeBufferManager::FindThreadToStealFrom() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(m_lock.OwnedByCurrentThread()); + } + CONTRACTL_END; + + // Find the thread buffer list containing the buffer whose most recent event is the oldest as long as the buffer is not + // the current buffer for the thread (e.g. it's next pointer is non-NULL). + // This means that the thread must also have multiple buffers, so that we don't steal its only buffer. + EventPipeBufferList *pOldestContainingList = NULL; + + SListElem *pElem = m_pPerThreadBufferList->GetHead(); + while(pElem != NULL) + { + EventPipeBufferList *pCandidate = pElem->GetValue(); + + // The current candidate has more than one buffer (otherwise it is disqualified). + if(pCandidate->GetHead()->GetNext() != NULL) + { + // If we haven't seen any candidates, this one automatically becomes the oldest candidate. + if(pOldestContainingList == NULL) + { + pOldestContainingList = pCandidate; + } + // Otherwise, to replace the existing candidate, this candidate must have an older timestamp in its oldest buffer. + else if((pOldestContainingList->GetHead()->GetMostRecentTimeStamp().QuadPart) > + (pCandidate->GetHead()->GetMostRecentTimeStamp().QuadPart)) + { + pOldestContainingList = pCandidate; + } + } + + pElem = m_pPerThreadBufferList->GetNext(pElem); + } + + return pOldestContainingList; +} + +void EventPipeBufferManager::DeAllocateBuffer(EventPipeBuffer *pBuffer) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if(pBuffer != NULL) + { + m_sizeOfAllBuffers -= pBuffer->GetSize(); + delete(pBuffer); +#ifdef _DEBUG + m_numBuffersAllocated--; +#endif // _DEBUG + } +} + +bool EventPipeBufferManager::WriteEvent(Thread *pThread, EventPipeEvent &event, BYTE *pData, unsigned int length, Thread *pEventThread, StackContents *pStack) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + // The input thread must match the current thread because no lock is taken on the buffer. + PRECONDITION(pThread == GetThread()); + } + CONTRACTL_END; + + _ASSERTE(pThread == GetThread()); + + // Check to see an event thread was specified. If not, then use the current thread. + if(pEventThread == NULL) + { + pEventThread = pThread; + } + + // Before we pick a buffer, make sure the event is enabled. + if(!event.IsEnabled()) + { + return false; + } + + // The event is still enabled. Mark that the thread is now writing an event. + pThread->SetEventWriteInProgress(true); + + // Check one more time to make sure that the event is still enabled. + // We do this because we might be trying to disable tracing and free buffers, so we + // must make sure that the event is enabled after we mark that we're writing to avoid + // races with the destructing thread. + if(!event.IsEnabled()) + { + return false; + } + + // See if the thread already has a buffer to try. + bool allocNewBuffer = false; + EventPipeBuffer *pBuffer = NULL; + EventPipeBufferList *pThreadBufferList = pThread->GetEventPipeBufferList(); + if(pThreadBufferList == NULL) + { + allocNewBuffer = true; + } + else + { + // The thread already has a buffer list. Select the newest buffer and attempt to write into it. + pBuffer = pThreadBufferList->GetTail(); + if(pBuffer == NULL) + { + // This should never happen. If the buffer list exists, it must contain at least one entry. + _ASSERT(!"Thread buffer list with zero entries encountered."); + return false; + } + else + { + // Attempt to write the event to the buffer. If this fails, we should allocate a new buffer. + allocNewBuffer = !pBuffer->WriteEvent(pEventThread, event, pData, length, pStack); + } + } + + // Check to see if we need to allocate a new buffer, and if so, do it here. + if(allocNewBuffer) + { + GCX_PREEMP(); + + unsigned int requestSize = sizeof(EventPipeEventInstance) + length; + pBuffer = AllocateBufferForThread(pThread, requestSize); + } + + // Try to write the event after we allocated (or stole) a buffer. + // This is the first time if the thread had no buffers before the call to this function. + // This is the second time if this thread did have one or more buffers, but they were full. + if(allocNewBuffer && pBuffer != NULL) + { + allocNewBuffer = !pBuffer->WriteEvent(pEventThread, event, pData, length, pStack); + } + + // Mark that the thread is no longer writing an event. + pThread->SetEventWriteInProgress(false); + +#ifdef _DEBUG + if(!allocNewBuffer) + { + InterlockedIncrement(&m_numEventsStored); + } +#endif // _DEBUG + return !allocNewBuffer; +} + +void EventPipeBufferManager::WriteAllBuffersToFile(EventPipeFile *pFile, LARGE_INTEGER stopTimeStamp) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pFile != NULL); + } + CONTRACTL_END; + + // TODO: Better version of merge sort. + // 1. Iterate through all of the threads, adding each buffer to a temporary list. + // 2. While iterating, get the lowest most recent timestamp. This is the timestamp that we want to process up to. + // 3. Process up to the lowest most recent timestamp for the set of buffers. + // 4. When we get NULLs from each of the buffers on PopNext(), we're done. + // 5. While iterating if PopNext() == NULL && Empty() == NULL, remove the buffer from the list. It's empty. + // 6. While iterating, grab the next lowest most recent timestamp. + // 7. Walk through the list again and look for any buffers that have a lower most recent timestamp than the next most recent timestamp. + // 8. If we find one, add it to the list and select its most recent timestamp as the lowest. + // 9. Process again (go to 3). + // 10. Continue until there are no more buffers to process. + + // Take the lock before walking the buffer list. + SpinLockHolder _slh(&m_lock); + + // Naively walk the circular buffer, writing the event stream in timestamp order. + while(true) + { + EventPipeEventInstance *pOldestInstance = NULL; + EventPipeBuffer *pOldestContainingBuffer = NULL; + EventPipeBufferList *pOldestContainingList = NULL; + SListElem *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. + break; + } + + // Write the oldest event. + pFile->WriteEvent(*pOldestInstance); +#ifdef _DEBUG + m_numEventsWritten++; +#endif // _DEBUG + + // Pop the event from the buffer. + pOldestContainingList->PopNextEvent(stopTimeStamp); + } +} + +void EventPipeBufferManager::DeAllocateBuffers() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE(EnsureConsistency()); + + // Take the thread store lock because we're going to iterate through the thread list. + { + ThreadStoreLockHolder tsl; + + // Take the buffer manager manipulation lock. + SpinLockHolder _slh(&m_lock); + + Thread *pThread = NULL; + while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) + { + // Get the thread's buffer list. + EventPipeBufferList *pBufferList = pThread->GetEventPipeBufferList(); + if(pBufferList != NULL) + { + // Attempt to free the buffer list. + // If the thread is using its buffer list skip it. + // This means we will leak a single buffer, but if tracing is re-enabled, that buffer can be used again. + if(!pThread->GetEventWriteInProgress()) + { + EventPipeBuffer *pBuffer = pBufferList->GetAndRemoveHead(); + while(pBuffer != NULL) + { + DeAllocateBuffer(pBuffer); + pBuffer = pBufferList->GetAndRemoveHead(); + } + + // Remove the list entry from the per thread buffer list. + SListElem *pElem = m_pPerThreadBufferList->GetHead(); + while(pElem != NULL) + { + EventPipeBufferList* pEntry = pElem->GetValue(); + if(pEntry == pBufferList) + { + pElem = m_pPerThreadBufferList->FindAndRemove(pElem); + + // In DEBUG, make sure that the element was found and removed. + _ASSERTE(pElem != NULL); + } + pElem = m_pPerThreadBufferList->GetNext(pElem); + } + + // Remove the list reference from the thread. + pThread->SetEventPipeBufferList(NULL); + + // Now that all of the list elements have been freed, free the list itself. + delete(pBufferList); + pBufferList = NULL; + } +#ifdef _DEBUG + else + { + // We can't deallocate the buffers. + m_numBuffersLeaked += pBufferList->GetCount(); + } +#endif // _DEBUG + } + } + } + + // Now that we've walked through all of the threads, let's see if there are any other buffers + // that belonged to threads that died during tracing. We can free these now. + + // Take the buffer manager manipulation lock + SpinLockHolder _slh(&m_lock); + + SListElem *pElem = m_pPerThreadBufferList->GetHead(); + while(pElem != NULL) + { + // Get the list and determine if we can free it. + EventPipeBufferList *pBufferList = pElem->GetValue(); + if(!pBufferList->OwnedByThread()) + { + // Iterate over all nodes in the list and de-allocate them. + EventPipeBuffer *pBuffer = pBufferList->GetAndRemoveHead(); + while(pBuffer != NULL) + { + DeAllocateBuffer(pBuffer); + pBuffer = pBufferList->GetAndRemoveHead(); + } + + // Remove the buffer list from the per-thread buffer list. + pElem = m_pPerThreadBufferList->FindAndRemove(pElem); + _ASSERTE(pElem != NULL); + + // Now that all of the list elements have been freed, free the list itself. + delete(pBufferList); + pBufferList = NULL; + } + + pElem = m_pPerThreadBufferList->GetNext(pElem); + } +} + +#ifdef _DEBUG +bool EventPipeBufferManager::EnsureConsistency() +{ + LIMITED_METHOD_CONTRACT; + + SListElem *pElem = m_pPerThreadBufferList->GetHead(); + while(pElem != NULL) + { + EventPipeBufferList *pBufferList = pElem->GetValue(); + + _ASSERTE(pBufferList->EnsureConsistency()); + + pElem = m_pPerThreadBufferList->GetNext(pElem); + } + + return true; +} +#endif // _DEBUG + +EventPipeBufferList::EventPipeBufferList(EventPipeBufferManager *pManager) +{ + LIMITED_METHOD_CONTRACT; + + m_pManager = pManager; + m_pHeadBuffer = NULL; + m_pTailBuffer = NULL; + m_bufferCount = 0; + m_pReadBuffer = NULL; + m_ownedByThread = true; + +#ifdef _DEBUG + m_pCreatingThread = GetThread(); +#endif // _DEBUG +} + +EventPipeBuffer* EventPipeBufferList::GetHead() +{ + LIMITED_METHOD_CONTRACT; + + return m_pHeadBuffer; +} + +EventPipeBuffer* EventPipeBufferList::GetTail() +{ + LIMITED_METHOD_CONTRACT; + + return m_pTailBuffer; +} + +void EventPipeBufferList::InsertTail(EventPipeBuffer *pBuffer) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pBuffer != NULL); + } + CONTRACTL_END; + + _ASSERTE(EnsureConsistency()); + + // Ensure that the input buffer didn't come from another list that was improperly cleaned up. + _ASSERTE((pBuffer->GetNext() == NULL) && (pBuffer->GetPrevious() == NULL)); + + // First node in the list. + if(m_pTailBuffer == NULL) + { + m_pHeadBuffer = m_pTailBuffer = pBuffer; + } + else + { + // Set links between the old and new tail nodes. + m_pTailBuffer->SetNext(pBuffer); + pBuffer->SetPrevious(m_pTailBuffer); + + // Set the new tail node. + m_pTailBuffer = pBuffer; + } + + m_bufferCount++; + + _ASSERTE(EnsureConsistency()); +} + +EventPipeBuffer* EventPipeBufferList::GetAndRemoveHead() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE(EnsureConsistency()); + + EventPipeBuffer *pRetBuffer = NULL; + if(m_pHeadBuffer != NULL) + { + // Save the head node. + pRetBuffer = m_pHeadBuffer; + + // Set the new head node. + m_pHeadBuffer = m_pHeadBuffer->GetNext(); + + // Update the head node's previous pointer. + if(m_pHeadBuffer != NULL) + { + m_pHeadBuffer->SetPrevious(NULL); + } + else + { + // We just removed the last buffer from the list. + // Make sure both head and tail pointers are NULL. + m_pTailBuffer = NULL; + } + + // Clear the next pointer of the old head node. + pRetBuffer->SetNext(NULL); + + // Ensure that the old head node has no dangling references. + _ASSERTE((pRetBuffer->GetNext() == NULL) && (pRetBuffer->GetPrevious() == NULL)); + + // Decrement the count of buffers in the list. + m_bufferCount--; + } + + _ASSERTE(EnsureConsistency()); + + return pRetBuffer; +} + +unsigned int EventPipeBufferList::GetCount() const +{ + LIMITED_METHOD_CONTRACT; + + return m_bufferCount; +} + +EventPipeEventInstance* EventPipeBufferList::PeekNextEvent(LARGE_INTEGER beforeTimeStamp, EventPipeBuffer **pContainingBuffer) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Get the current read buffer. + // If it's not set, start with the head buffer. + if(m_pReadBuffer == NULL) + { + m_pReadBuffer = m_pHeadBuffer; + } + + // If the read buffer is still NULL, then this list contains no buffers. + if(m_pReadBuffer == NULL) + { + return NULL; + } + + // Get the next event in the buffer. + EventPipeEventInstance *pNext = m_pReadBuffer->PeekNext(beforeTimeStamp); + + // If the next event is NULL, then go to the next buffer. + if(pNext == NULL) + { + m_pReadBuffer = m_pReadBuffer->GetNext(); + if(m_pReadBuffer != NULL) + { + pNext = m_pReadBuffer->PeekNext(beforeTimeStamp); + } + } + + // Set the containing buffer. + if(pNext != NULL && pContainingBuffer != NULL) + { + *pContainingBuffer = m_pReadBuffer; + } + + // Make sure pContainingBuffer is properly set. + _ASSERTE((pNext == NULL) || (pNext != NULL && pContainingBuffer == NULL) || (pNext != NULL && *pContainingBuffer == m_pReadBuffer)); + return pNext; +} + +EventPipeEventInstance* EventPipeBufferList::PopNextEvent(LARGE_INTEGER beforeTimeStamp) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Get the next event. + EventPipeBuffer *pContainingBuffer = NULL; + EventPipeEventInstance *pNext = PeekNextEvent(beforeTimeStamp, &pContainingBuffer); + + // If the event is non-NULL, pop it. + if(pNext != NULL && pContainingBuffer != 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); + EventPipeBuffer *pRemoved = GetAndRemoveHead(); + _ASSERTE(pRemoved == pContainingBuffer); + + // 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; + } + } + + return pNext; +} + +bool EventPipeBufferList::OwnedByThread() +{ + LIMITED_METHOD_CONTRACT; + return m_ownedByThread; +} + +void EventPipeBufferList::SetOwnedByThread(bool value) +{ + LIMITED_METHOD_CONTRACT; + m_ownedByThread = value; +} + +#ifdef _DEBUG +Thread* EventPipeBufferList::GetThread() +{ + LIMITED_METHOD_CONTRACT; + + return m_pCreatingThread; +} + +bool EventPipeBufferList::EnsureConsistency() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Either the head and tail nodes are both NULL or both are non-NULL. + _ASSERTE((m_pHeadBuffer == NULL && m_pTailBuffer == NULL) || (m_pHeadBuffer != NULL && m_pTailBuffer != NULL)); + + // If the list is NULL, check the count and return. + if(m_pHeadBuffer == NULL) + { + _ASSERTE(m_bufferCount == 0); + return true; + } + + // If the list is non-NULL, walk the list forward until we get to the end. + unsigned int nodeCount = (m_pHeadBuffer != NULL) ? 1 : 0; + EventPipeBuffer *pIter = m_pHeadBuffer; + while(pIter->GetNext() != NULL) + { + pIter = pIter->GetNext(); + nodeCount++; + + // Check for consistency of the buffer itself. + _ASSERTE(pIter->EnsureConsistency()); + + // Check for cycles. + _ASSERTE(nodeCount <= m_bufferCount); + } + + // When we're done with the walk, pIter must point to the tail node. + _ASSERTE(pIter == m_pTailBuffer); + + // Node count must equal the buffer count. + _ASSERTE(nodeCount == m_bufferCount); + + // Now, walk the list in reverse. + pIter = m_pTailBuffer; + nodeCount = (m_pTailBuffer != NULL) ? 1 : 0; + while(pIter->GetPrevious() != NULL) + { + pIter = pIter->GetPrevious(); + nodeCount++; + + // Check for cycles. + _ASSERTE(nodeCount <= m_bufferCount); + } + + // When we're done with the reverse walk, pIter must point to the head node. + _ASSERTE(pIter == m_pHeadBuffer); + + // Node count must equal the buffer count. + _ASSERTE(nodeCount == m_bufferCount); + + // We're done. + return true; +} +#endif // _DEBUG + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipebuffermanager.h b/src/vm/eventpipebuffermanager.h new file mode 100644 index 0000000..74783d2 --- /dev/null +++ b/src/vm/eventpipebuffermanager.h @@ -0,0 +1,161 @@ +// 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. + +#ifndef __EVENTPIPE_BUFFERMANAGER_H__ +#define __EVENTPIPE_BUFFERMANAGER_H__ + +#ifdef FEATURE_PERFTRACING + +#include "eventpipefile.h" +#include "eventpipebuffer.h" +#include "spinlock.h" + +class EventPipeBufferList; + +class EventPipeBufferManager +{ + + // Declare friends. + friend class EventPipeBufferList; + +private: + + // A list of linked-lists of buffer objects. + // Each entry in this list represents a set of buffers owned by a single thread. + // The actual Thread object has a pointer to the object contained in this list. This ensures that + // each thread can access its own list, while at the same time, ensuring that when + // a thread is destroyed, we keep the buffers around without having to perform any + // migration or book-keeping. + SList> *m_pPerThreadBufferList; + + // The total allocation size of buffers under management. + size_t m_sizeOfAllBuffers; + + // Lock to protect access to the per-thread buffer list and total allocation size. + SpinLock m_lock; + +#ifdef _DEBUG + // For debugging purposes. + unsigned int m_numBuffersAllocated; + unsigned int m_numBuffersStolen; + unsigned int m_numBuffersLeaked; + Volatile m_numEventsStored; + 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); + + // Add a buffer to the thread buffer list. + void AddBufferToThreadBufferList(EventPipeBufferList *pThreadBuffers, EventPipeBuffer *pBuffer); + + // Find the thread that owns the oldest buffer that is eligible to be stolen. + EventPipeBufferList* FindThreadToStealFrom(); + + // De-allocates the input buffer. + void DeAllocateBuffer(EventPipeBuffer *pBuffer); + +public: + + EventPipeBufferManager(); + + // Write an event to the input thread's current event buffer. + // An optional eventThread can be provided for sample profiler events. + // This is because the thread that writes the events is not the same as the "event thread". + // An optional stack trace can be provided for sample profiler events. + // Otherwise, if a stack trace is needed, one will be automatically collected. + bool WriteEvent(Thread *pThread, EventPipeEvent &event, BYTE *pData, unsigned int length, Thread *pEventThread = NULL, StackContents *pStack = NULL); + + // Write the contents of the managed buffers to the specified file. + // The stopTimeStamp is used to determine when tracing was stopped to ensure that we + // skip any events that might be partially written due to races when tracing is stopped. + void WriteAllBuffersToFile(EventPipeFile *pFile, LARGE_INTEGER stopTimeStamp); + + // Attempt to de-allocate resources as best we can. It is possible for some buffers to leak because + // threads can be in the middle of a write operation and get blocked, and we may not get an opportunity + // to free their buffer for a very long time. + void DeAllocateBuffers(); + +#ifdef _DEBUG + bool EnsureConsistency(); +#endif // _DEBUG +}; + +// Represents a list of buffers associated with a specific thread. +class EventPipeBufferList +{ +private: + + // The buffer manager that owns this list. + EventPipeBufferManager *m_pManager; + + // Buffers are stored in an intrusive linked-list from oldest to newest. + // Head is the oldest buffer. Tail is the newest (and currently used) buffer. + EventPipeBuffer *m_pHeadBuffer; + EventPipeBuffer *m_pTailBuffer; + + // The number of buffers in the list. + unsigned int m_bufferCount; + + // The current read buffer (used when processing events on tracing stop). + EventPipeBuffer *m_pReadBuffer; + + // True if this thread is owned by a thread. + // If it is false, then this buffer can be de-allocated after it is drained. + Volatile m_ownedByThread; + +#ifdef _DEBUG + // For diagnostics, keep the thread pointer. + Thread *m_pCreatingThread; +#endif // _DEBUG + +public: + + EventPipeBufferList(EventPipeBufferManager *pManager); + + // Get the head node of the list. + EventPipeBuffer* GetHead(); + + // Get the tail node of the list. + EventPipeBuffer* GetTail(); + + // Insert a new buffer at the tail of the list. + void InsertTail(EventPipeBuffer *pBuffer); + + // Remove the head node of the list. + EventPipeBuffer* GetAndRemoveHead(); + + // Get the count of buffers in the list. + unsigned int GetCount() const; + + // Get the next event as long as it is before the specified timestamp. + EventPipeEventInstance* PeekNextEvent(LARGE_INTEGER beforeTimeStamp, EventPipeBuffer **pContainingBuffer); + + // Get the next event as long as it is before the specified timestamp, and also mark it as read. + EventPipeEventInstance* PopNextEvent(LARGE_INTEGER beforeTimeStamp); + + // True if a thread owns this list. + bool OwnedByThread(); + + // Set whether or not this list is owned by a thread. + // If it is not owned by a thread, then it can be de-allocated + // after the buffer is drained. + // The default value is true. + void SetOwnedByThread(bool value); + +#ifdef _DEBUG + // Get the thread associated with this list. + Thread* GetThread(); + + // Validate the consistency of the list. + // This function will assert if the list is in an inconsistent state. + bool EnsureConsistency(); +#endif // _DEBUG +}; + +#endif // FEATURE_PERFTRACING + +#endif // __EVENTPIPE_BUFFERMANAGER_H__ diff --git a/src/vm/eventpipeconfiguration.cpp b/src/vm/eventpipeconfiguration.cpp index 0128685..73fa963 100644 --- a/src/vm/eventpipeconfiguration.cpp +++ b/src/vm/eventpipeconfiguration.cpp @@ -18,6 +18,9 @@ EventPipeConfiguration::EventPipeConfiguration() { STANDARD_VM_CONTRACT; + m_enabled = false; + m_circularBufferSizeInBytes = 1024 * 1024 * 1000; // Default to 1000MB. + m_pEnabledProviderList = NULL; m_pProviderList = new SList>(); } @@ -31,6 +34,12 @@ EventPipeConfiguration::~EventPipeConfiguration() } CONTRACTL_END; + if(m_pEnabledProviderList != NULL) + { + delete(m_pEnabledProviderList); + m_pEnabledProviderList = NULL; + } + if(m_pProviderList != NULL) { delete(m_pProviderList); @@ -56,7 +65,7 @@ void EventPipeConfiguration::Initialize() 0, /* keywords */ 0, /* eventID */ 0, /* eventVersion */ - EventPipeEventLevel::Critical, + EventPipeEventLevel::LogAlways, false); /* needStack */ } @@ -83,9 +92,18 @@ bool EventPipeConfiguration::RegisterProvider(EventPipeProvider &provider) // The provider has not been registered, so register it. m_pProviderList->InsertTail(new SListElem(&provider)); - // TODO: Set the provider configuration and enable it if we know - // anything about the provider before it is registered. - provider.SetConfiguration(true /* providerEnabled */, 0xFFFFFFFFFFFFFFFF /* keywords */, EventPipeEventLevel::Verbose /* level */); + // Set the provider configuration and enable it if we know anything about the provider before it is registered. + if(m_pEnabledProviderList != NULL) + { + EventPipeEnabledProvider *pEnabledProvider = m_pEnabledProviderList->GetEnabledProvider(&provider); + if(pEnabledProvider != NULL) + { + provider.SetConfiguration( + true /* providerEnabled */, + pEnabledProvider->GetKeywords(), + pEnabledProvider->GetLevel()); + } + } return true; } @@ -170,7 +188,27 @@ EventPipeProvider* EventPipeConfiguration::GetProviderNoLock(const GUID &provide return NULL; } -void EventPipeConfiguration::Enable() +size_t EventPipeConfiguration::GetCircularBufferSize() const +{ + LIMITED_METHOD_CONTRACT; + + return m_circularBufferSizeInBytes; +} + +void EventPipeConfiguration::SetCircularBufferSize(size_t circularBufferSize) +{ + LIMITED_METHOD_CONTRACT; + + if(!m_enabled) + { + m_circularBufferSizeInBytes = circularBufferSize; + } +} + +void EventPipeConfiguration::Enable( + uint circularBufferSizeInMB, + EventPipeProviderConfiguration *pProviders, + int numProviders) { CONTRACTL { @@ -182,12 +220,24 @@ void EventPipeConfiguration::Enable() } CONTRACTL_END; + m_circularBufferSizeInBytes = circularBufferSizeInMB * 1024 * 1024; + m_pEnabledProviderList = new EventPipeEnabledProviderList(pProviders, static_cast(numProviders)); + m_enabled = true; + SListElem *pElem = m_pProviderList->GetHead(); while(pElem != NULL) { - // TODO: Only enable the providers that have been explicitly enabled with specified keywords/level. EventPipeProvider *pProvider = pElem->GetValue(); - pProvider->SetConfiguration(true /* providerEnabled */, 0xFFFFFFFFFFFFFFFF /* keywords */, EventPipeEventLevel::Verbose /* level */); + + // Enable the provider if it has been configured. + EventPipeEnabledProvider *pEnabledProvider = m_pEnabledProviderList->GetEnabledProvider(pProvider); + if(pEnabledProvider != NULL) + { + pProvider->SetConfiguration( + true /* providerEnabled */, + pEnabledProvider->GetKeywords(), + pEnabledProvider->GetLevel()); + } pElem = m_pProviderList->GetNext(pElem); } @@ -214,9 +264,24 @@ void EventPipeConfiguration::Disable() pElem = m_pProviderList->GetNext(pElem); } + + m_enabled = false; + + // Free the enabled providers list. + if(m_pEnabledProviderList != NULL) + { + delete(m_pEnabledProviderList); + m_pEnabledProviderList = NULL; + } +} + +bool EventPipeConfiguration::Enabled() const +{ + LIMITED_METHOD_CONTRACT; + return m_enabled; } -EventPipeEventInstance* EventPipeConfiguration::BuildEventMetadataEvent(EventPipeEvent &sourceEvent, BYTE *pPayloadData, unsigned int payloadLength) +EventPipeEventInstance* EventPipeConfiguration::BuildEventMetadataEvent(EventPipeEventInstance &sourceInstance, BYTE *pPayloadData, unsigned int payloadLength) { CONTRACTL { @@ -233,6 +298,7 @@ EventPipeEventInstance* EventPipeConfiguration::BuildEventMetadataEvent(EventPip // - Optional event description payload. // Calculate the size of the event. + EventPipeEvent &sourceEvent = *sourceInstance.GetEvent(); const GUID &providerID = sourceEvent.GetProvider()->GetProviderID(); unsigned int eventID = sourceEvent.GetEventID(); unsigned int eventVersion = sourceEvent.GetEventVersion(); @@ -266,7 +332,192 @@ EventPipeEventInstance* EventPipeConfiguration::BuildEventMetadataEvent(EventPip pInstancePayload, instancePayloadSize); + // Set the timestamp to match the source event, because the metadata event + // will be emitted right before the source event. + pInstance->SetTimeStamp(sourceInstance.GetTimeStamp()); + return pInstance; } +EventPipeEnabledProviderList::EventPipeEnabledProviderList( + EventPipeProviderConfiguration *pConfigs, + unsigned int numConfigs) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Test COMPLUS variable to enable tracing at start-up. + // If tracing is enabled at start-up create the catch-all provider and always return it. + if((CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PerformanceTracing) & 1) == 1) + { + m_pCatchAllProvider = new EventPipeEnabledProvider(); + m_pCatchAllProvider->Set(NULL, 0xFFFFFFFF, EventPipeEventLevel::Verbose); + m_pProviders = NULL; + m_numProviders = 0; + return; + } + + m_pCatchAllProvider = NULL; + m_numProviders = numConfigs; + if(m_numProviders == 0) + { + return; + } + + m_pProviders = new EventPipeEnabledProvider[m_numProviders]; + for(int i=0; iGetProviderID(), wszProviderID, guidSize)) + { + wszProviderID[0] = '\0'; + } + + // Strip off the {}. + SString providerNameStr(&wszProviderID[1], guidSize-3); + LPCWSTR providerName = providerNameStr.GetUnicode(); + + EventPipeEnabledProvider *pEnabledProvider = NULL; + for(int i=0; iGetProviderName()) == 0) + { + pEnabledProvider = pCandidate; + break; + } + } + } + + return pEnabledProvider; +} + +EventPipeEnabledProvider::EventPipeEnabledProvider() +{ + LIMITED_METHOD_CONTRACT; + m_pProviderName = NULL; + m_keywords = 0; +} + +EventPipeEnabledProvider::~EventPipeEnabledProvider() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if(m_pProviderName != NULL) + { + delete[] m_pProviderName; + m_pProviderName = NULL; + } +} + +void EventPipeEnabledProvider::Set(LPCWSTR providerName, UINT64 keywords, EventPipeEventLevel loggingLevel) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if(m_pProviderName != NULL) + { + delete(m_pProviderName); + m_pProviderName = NULL; + } + + if(providerName != NULL) + { + unsigned int bufSize = wcslen(providerName) + 1; + m_pProviderName = new WCHAR[bufSize]; + wcscpy_s(m_pProviderName, bufSize, providerName); + } + m_keywords = keywords; + m_loggingLevel = loggingLevel; +} + +LPCWSTR EventPipeEnabledProvider::GetProviderName() const +{ + LIMITED_METHOD_CONTRACT; + return m_pProviderName; +} + +UINT64 EventPipeEnabledProvider::GetKeywords() const +{ + LIMITED_METHOD_CONTRACT; + return m_keywords; +} + +EventPipeEventLevel EventPipeEnabledProvider::GetLevel() const +{ + LIMITED_METHOD_CONTRACT; + return m_loggingLevel; +} + #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipeconfiguration.h b/src/vm/eventpipeconfiguration.h index a237775..baa9b3b 100644 --- a/src/vm/eventpipeconfiguration.h +++ b/src/vm/eventpipeconfiguration.h @@ -8,9 +8,22 @@ #include "slist.h" +class EventPipeEnabledProvider; +class EventPipeEnabledProviderList; class EventPipeEvent; class EventPipeEventInstance; class EventPipeProvider; +struct EventPipeProviderConfiguration; + +enum class EventPipeEventLevel +{ + LogAlways, + Critical, + Error, + Warning, + Informational, + Verbose +}; class EventPipeConfiguration { @@ -31,20 +44,42 @@ public: // Get the provider with the specified provider ID if it exists. EventPipeProvider* GetProvider(const GUID &providerID); + // Get the configured size of the circular buffer. + size_t GetCircularBufferSize() const; + + // Set the configured size of the circular buffer. + void SetCircularBufferSize(size_t circularBufferSize); + // Enable the event pipe. - void Enable(); + void Enable( + uint circularBufferSizeInMB, + EventPipeProviderConfiguration *pProviders, + int numProviders); // Disable the event pipe. void Disable(); + // Get the status of the event pipe. + bool Enabled() const; + // Get the event used to write metadata to the event stream. - EventPipeEventInstance* BuildEventMetadataEvent(EventPipeEvent &sourceEvent, BYTE *pPayloadData = NULL, unsigned int payloadLength = 0); + EventPipeEventInstance* BuildEventMetadataEvent(EventPipeEventInstance &sourceInstance, BYTE *pPayloadData = NULL, unsigned int payloadLength = 0); private: // Get the provider without taking the lock. EventPipeProvider* GetProviderNoLock(const GUID &providerID); + // Determines whether or not the event pipe is enabled. + Volatile m_enabled; + + // The configured size of the circular buffer. + size_t m_circularBufferSizeInBytes; + + // EventPipeConfiguration only supports a single session. + // This is the set of configurations for each enabled provider. + EventPipeEnabledProviderList *m_pEnabledProviderList; + // The list of event pipe providers. SList> *m_pProviderList; @@ -59,6 +94,59 @@ private: static const GUID s_configurationProviderID; }; +class EventPipeEnabledProviderList +{ + +private: + + // The number of providers in the list. + unsigned int m_numProviders; + + // The list of providers. + EventPipeEnabledProvider *m_pProviders; + + // A catch-all provider used when tracing is enabled at start-up + // under (COMPlus_PerformanceTracing & 1) == 1. + EventPipeEnabledProvider *m_pCatchAllProvider; + +public: + + // Create a new list based on the input. + EventPipeEnabledProviderList(EventPipeProviderConfiguration *pConfigs, unsigned int numConfigs); + ~EventPipeEnabledProviderList(); + + // Get the enabled provider for the specified provider. + // Return NULL if one doesn't exist. + EventPipeEnabledProvider* GetEnabledProvider(EventPipeProvider *pProvider); +}; + +class EventPipeEnabledProvider +{ +private: + + // The provider name. + WCHAR *m_pProviderName; + + // The enabled keywords. + UINT64 m_keywords; + + // The loging level. + EventPipeEventLevel m_loggingLevel; + +public: + + EventPipeEnabledProvider(); + ~EventPipeEnabledProvider(); + + void Set(LPCWSTR providerName, UINT64 keywords, EventPipeEventLevel loggingLevel); + + LPCWSTR GetProviderName() const; + + UINT64 GetKeywords() const; + + EventPipeEventLevel GetLevel() const; +}; + #endif // FEATURE_PERFTRACING #endif // __EVENTPIPE_CONFIGURATION_H__ diff --git a/src/vm/eventpipeevent.h b/src/vm/eventpipeevent.h index 9e42615..3076617 100644 --- a/src/vm/eventpipeevent.h +++ b/src/vm/eventpipeevent.h @@ -35,7 +35,7 @@ private: bool m_needStack; // True if the event is current enabled. - bool m_enabled; + Volatile m_enabled; // Refreshes the runtime state for this event. // Called by EventPipeProvider when the provider configuration changes. diff --git a/src/vm/eventpipeeventinstance.cpp b/src/vm/eventpipeeventinstance.cpp index 2bf500b..7877b79 100644 --- a/src/vm/eventpipeeventinstance.cpp +++ b/src/vm/eventpipeeventinstance.cpp @@ -24,6 +24,10 @@ EventPipeEventInstance::EventPipeEventInstance( } CONTRACTL_END; +#ifdef _DEBUG + m_debugEventStart = 0xDEADBEEF; + m_debugEventEnd = 0xCAFEBABE; +#endif // _DEBUG m_pEvent = &event; m_threadID = threadID; m_pData = pData; @@ -34,6 +38,10 @@ EventPipeEventInstance::EventPipeEventInstance( { EventPipe::WalkManagedStackForCurrentThread(m_stackContents); } + +#ifdef _DEBUG + EnsureConsistency(); +#endif // _DEBUG } StackContents* EventPipeEventInstance::GetStack() @@ -50,6 +58,13 @@ EventPipeEvent* EventPipeEventInstance::GetEvent() const return m_pEvent; } +LARGE_INTEGER EventPipeEventInstance::GetTimeStamp() const +{ + LIMITED_METHOD_CONTRACT; + + return m_timeStamp; +} + BYTE* EventPipeEventInstance::GetData() const { LIMITED_METHOD_CONTRACT; @@ -113,6 +128,7 @@ void EventPipeEventInstance::FastSerialize(FastSerializer *pSerializer, StreamLa } } +#ifdef _DEBUG void EventPipeEventInstance::SerializeToJsonFile(EventPipeJsonFile *pFile) { CONTRACTL @@ -147,6 +163,35 @@ void EventPipeEventInstance::SerializeToJsonFile(EventPipeJsonFile *pFile) } EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); } +#endif + +void EventPipeEventInstance::SetTimeStamp(LARGE_INTEGER timeStamp) +{ + LIMITED_METHOD_CONTRACT; + + m_timeStamp = timeStamp; +} + +#ifdef _DEBUG +bool EventPipeEventInstance::EnsureConsistency() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Validate event start. + _ASSERTE(m_debugEventStart == 0xDEADBEEF); + + // Validate event end. + _ASSERTE(m_debugEventEnd == 0xCAFEBABE); + + return true; +} +#endif // _DEBUG SampleProfilerEventInstance::SampleProfilerEventInstance(Thread *pThread) :EventPipeEventInstance(*SampleProfiler::s_pThreadTimeEvent, pThread->GetOSThreadId(), NULL, 0) diff --git a/src/vm/eventpipeeventinstance.h b/src/vm/eventpipeeventinstance.h index 84ad566..7e0ea47 100644 --- a/src/vm/eventpipeeventinstance.h +++ b/src/vm/eventpipeeventinstance.h @@ -14,6 +14,8 @@ class EventPipeEventInstance { + // Declare friends. + friend EventPipeConfiguration; public: @@ -25,6 +27,9 @@ public: // Get the stack contents object to either read or write to it. StackContents* GetStack(); + // Get the timestamp. + LARGE_INTEGER GetTimeStamp() const; + // Get a pointer to the data payload. BYTE* GetData() const; @@ -34,11 +39,19 @@ public: // Serialize this object using FastSerialization. void FastSerialize(FastSerializer *pSerializer, StreamLabel metadataLabel); +#ifdef _DEBUG // Serialize this event to the JSON file. void SerializeToJsonFile(EventPipeJsonFile *pFile); + bool EnsureConsistency(); +#endif // _DEBUG + protected: +#ifdef _DEBUG + unsigned int m_debugEventStart; +#endif // _DEBUG + EventPipeEvent *m_pEvent; DWORD m_threadID; LARGE_INTEGER m_timeStamp; @@ -46,6 +59,17 @@ protected: BYTE *m_pData; unsigned int m_dataLength; StackContents m_stackContents; + +#ifdef _DEBUG + unsigned int m_debugEventEnd; +#endif // _DEBUG + +private: + + // This is used for metadata events by EventPipeConfiguration because + // the metadata event is created after the first instance of the event + // but must be inserted into the file before the first instance of the event. + void SetTimeStamp(LARGE_INTEGER timeStamp); }; // A specific type of event instance for use by the SampleProfiler. diff --git a/src/vm/eventpipefile.cpp b/src/vm/eventpipefile.cpp index 895f732..f574814 100644 --- a/src/vm/eventpipefile.cpp +++ b/src/vm/eventpipefile.cpp @@ -3,12 +3,19 @@ // See the LICENSE file in the project root for more information. #include "common.h" +#include "eventpipebuffer.h" #include "eventpipeconfiguration.h" #include "eventpipefile.h" #ifdef FEATURE_PERFTRACING -EventPipeFile::EventPipeFile(SString &outputFilePath) +EventPipeFile::EventPipeFile( + SString &outputFilePath +#ifdef _DEBUG + , + bool lockOnWrite +#endif // _DEBUG +) { CONTRACTL { @@ -22,6 +29,10 @@ EventPipeFile::EventPipeFile(SString &outputFilePath) m_serializationLock.Init(LOCK_TYPE_DEFAULT); m_pMetadataLabels = new MapSHashWithRemove(); +#ifdef _DEBUG + m_lockOnWrite = lockOnWrite; +#endif // _DEBUG + // File start time information. GetSystemTime(&m_fileOpenSystemTime); QueryPerformanceCounter(&m_fileOpenTimeStamp); @@ -78,22 +89,29 @@ void EventPipeFile::WriteEvent(EventPipeEventInstance &instance) } CONTRACTL_END; - // Take the serialization lock. - SpinLockHolder _slh(&m_serializationLock); +#ifdef _DEBUG + if(m_lockOnWrite) + { + // Take the serialization lock. + // This is used for synchronous file writes. + // The circular buffer path only writes from one thread. + SpinLockHolder _slh(&m_serializationLock); + } +#endif // _DEBUG // Check to see if we've seen this event type before. // If not, then write the event metadata to the event stream first. StreamLabel metadataLabel = GetMetadataLabel(*instance.GetEvent()); if(metadataLabel == 0) { - EventPipeEventInstance* pMetadataInstance = EventPipe::GetConfiguration()->BuildEventMetadataEvent(*instance.GetEvent()); + EventPipeEventInstance* pMetadataInstance = EventPipe::GetConfiguration()->BuildEventMetadataEvent(instance); metadataLabel = m_pSerializer->GetStreamLabel(); pMetadataInstance->FastSerialize(m_pSerializer, (StreamLabel)0); // 0 breaks recursion and represents the metadata event. SaveMetadataLabel(*instance.GetEvent(), metadataLabel); - delete (pMetadataInstance->GetData()); + delete[] (pMetadataInstance->GetData()); delete (pMetadataInstance); } diff --git a/src/vm/eventpipefile.h b/src/vm/eventpipefile.h index 1fbb4c0..2f68535 100644 --- a/src/vm/eventpipefile.h +++ b/src/vm/eventpipefile.h @@ -16,7 +16,13 @@ class EventPipeFile : public FastSerializableObject { public: - EventPipeFile(SString &outputFilePath); + + EventPipeFile(SString &outputFilePath +#ifdef _DEBUG + , + bool lockOnWrite = false +#endif // _DEBUG + ); ~EventPipeFile(); // Write an event to the file. @@ -68,6 +74,10 @@ class EventPipeFile : public FastSerializableObject // Hashtable of metadata labels. MapSHashWithRemove *m_pMetadataLabels; + +#ifdef _DEBUG + bool m_lockOnWrite; +#endif // _DEBUG }; #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipejsonfile.cpp b/src/vm/eventpipejsonfile.cpp index ea2dd29..f769590 100644 --- a/src/vm/eventpipejsonfile.cpp +++ b/src/vm/eventpipejsonfile.cpp @@ -5,6 +5,7 @@ #include "common.h" #include "eventpipejsonfile.h" +#ifdef _DEBUG #ifdef FEATURE_PERFTRACING EventPipeJsonFile::EventPipeJsonFile(SString &outFilePath) @@ -140,4 +141,5 @@ void EventPipeJsonFile::FormatCallStack(StackContents &stackContents, SString &r } } +#endif // _DEBUG #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipejsonfile.h b/src/vm/eventpipejsonfile.h index 2b836d2..7686db7 100644 --- a/src/vm/eventpipejsonfile.h +++ b/src/vm/eventpipejsonfile.h @@ -6,6 +6,7 @@ #ifndef __EVENTPIPE_JSONFILE_H__ #define __EVENTPIPE_JSONFILE_H__ +#ifdef _DEBUG #ifdef FEATURE_PERFTRACING #include "common.h" @@ -44,5 +45,6 @@ class EventPipeJsonFile }; #endif // FEATURE_PERFTRACING +#endif // _DEBUG #endif // __EVENTPIPE_JSONFILE_H__ diff --git a/src/vm/eventpipeprovider.cpp b/src/vm/eventpipeprovider.cpp index 362c37a..be87fb4 100644 --- a/src/vm/eventpipeprovider.cpp +++ b/src/vm/eventpipeprovider.cpp @@ -27,11 +27,11 @@ EventPipeProvider::EventPipeProvider(const GUID &providerID, EventPipeCallback p m_pEventList = new SList>(); m_pCallbackFunction = pCallbackFunction; m_pCallbackData = pCallbackData; + m_pConfig = EventPipe::GetConfiguration(); + _ASSERTE(m_pConfig != NULL); // Register the provider. - EventPipeConfiguration* pConfig = EventPipe::GetConfiguration(); - _ASSERTE(pConfig != NULL); - pConfig->RegisterProvider(*this); + m_pConfig->RegisterProvider(*this); } EventPipeProvider::~EventPipeProvider() @@ -46,6 +46,9 @@ EventPipeProvider::~EventPipeProvider() // Unregister the provider. // This call is re-entrant. + // NOTE: We don't use the cached event pipe configuration pointer + // in case this runs during shutdown and the configuration has already + // been freed. EventPipeConfiguration* pConfig = EventPipe::GetConfiguration(); _ASSERTE(pConfig != NULL); pConfig->UnregisterProvider(*this); @@ -81,7 +84,7 @@ bool EventPipeProvider::Enabled() const { LIMITED_METHOD_CONTRACT; - return m_enabled; + return (m_pConfig->Enabled() && m_enabled); } bool EventPipeProvider::EventEnabled(INT64 keywords) const @@ -163,6 +166,7 @@ void EventPipeProvider::AddEvent(EventPipeEvent &event) CrstHolder _crst(EventPipe::GetLock()); m_pEventList->InsertTail(new SListElem(&event)); + event.RefreshState(); } void EventPipeProvider::InvokeCallback() diff --git a/src/vm/eventpipeprovider.h b/src/vm/eventpipeprovider.h index d1ce158..7aaa8e4 100644 --- a/src/vm/eventpipeprovider.h +++ b/src/vm/eventpipeprovider.h @@ -7,6 +7,7 @@ #ifdef FEATURE_PERFTRACING +#include "eventpipeconfiguration.h" #include "slist.h" class EventPipeEvent; @@ -21,16 +22,6 @@ typedef void (*EventPipeCallback)( void *FilterData, void *CallbackContext); -enum class EventPipeEventLevel -{ - LogAlways, - Critical, - Error, - Warning, - Informational, - Verbose -}; - class EventPipeProvider { // Declare friends. @@ -59,6 +50,9 @@ private: // The optional provider callback data pointer. void *m_pCallbackData; + // The configuration object. + EventPipeConfiguration *m_pConfig; + public: EventPipeProvider(const GUID &providerID, EventPipeCallback pCallbackFunction = NULL, void *pCallbackData = NULL); diff --git a/src/vm/sampleprofiler.cpp b/src/vm/sampleprofiler.cpp index 6a6a23a..7c6429a 100644 --- a/src/vm/sampleprofiler.cpp +++ b/src/vm/sampleprofiler.cpp @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. #include "common.h" +#include "eventpipebuffermanager.h" #include "eventpipeeventinstance.h" #include "sampleprofiler.h" #include "hosting.h" @@ -136,7 +137,7 @@ DWORD WINAPI SampleProfiler::ThreadProc(void *args) } } - // Destroy the sampling thread when done running. + // Destroy the sampling thread when it is done running. DestroyThread(s_pSamplingThread); s_pSamplingThread = NULL; @@ -158,19 +159,18 @@ void SampleProfiler::WalkManagedThreads() } CONTRACTL_END; - Thread *pThread = NULL; + Thread *pTargetThread = NULL; // Iterate over all managed threads. // Assumes that the ThreadStoreLock is held because we've suspended all threads. - while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) + while ((pTargetThread = ThreadStore::GetThreadList(pTargetThread)) != NULL) { - SampleProfilerEventInstance instance(pThread); - StackContents &stackContents = *(instance.GetStack()); + StackContents stackContents; // Walk the stack and write it out as an event. - if(EventPipe::WalkManagedStackForThread(pThread, stackContents) && !stackContents.IsEmpty()) + if(EventPipe::WalkManagedStackForThread(pTargetThread, stackContents) && !stackContents.IsEmpty()) { - EventPipe::WriteSampleProfileEvent(instance); + EventPipe::WriteSampleProfileEvent(s_pSamplingThread, pTargetThread, stackContents); } } } diff --git a/src/vm/sampleprofiler.h b/src/vm/sampleprofiler.h index 5ad388d..19deb08 100644 --- a/src/vm/sampleprofiler.h +++ b/src/vm/sampleprofiler.h @@ -14,6 +14,7 @@ class SampleProfiler { // Declare friends. + friend class EventPipe; friend class SampleProfilerEventInstance; public: diff --git a/src/vm/threads.cpp b/src/vm/threads.cpp index c36232e..df8916c 100644 --- a/src/vm/threads.cpp +++ b/src/vm/threads.cpp @@ -54,6 +54,10 @@ #include "olecontexthelpers.h" #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT +#ifdef FEATURE_PERFTRACING +#include "eventpipebuffermanager.h" +#endif // FEATURE_PERFTRACING + SPTR_IMPL(ThreadStore, ThreadStore, s_pThreadStore); @@ -988,6 +992,16 @@ void DestroyThread(Thread *th) th->SetThreadState(Thread::TS_ReportDead); th->OnThreadTerminate(FALSE); } + +#ifdef FEATURE_PERFTRACING + // Before the thread dies, mark its buffers as no longer owned + // so that they can be cleaned up after the thread dies. + EventPipeBufferList *pBufferList = th->GetEventPipeBufferList(); + if(pBufferList != NULL) + { + pBufferList->SetOwnedByThread(false); + } +#endif // FEATURE_PERFTRACING } //------------------------------------------------------------------------- @@ -1084,6 +1098,16 @@ HRESULT Thread::DetachThread(BOOL fDLLThreadDetach) m_pClrDebugState = NULL; #endif //ENABLE_CONTRACTS_DATA +#ifdef FEATURE_PERFTRACING + // Before the thread dies, mark its buffers as no longer owned + // so that they can be cleaned up after the thread dies. + EventPipeBufferList *pBufferList = m_pEventPipeBufferList.Load(); + if(pBufferList != NULL) + { + pBufferList->SetOwnedByThread(false); + } +#endif // FEATURE_PERFTRACING + FastInterlockOr((ULONG*)&m_State, (int) (Thread::TS_Detached | Thread::TS_ReportDead)); // Do not touch Thread object any more. It may be destroyed. @@ -2008,6 +2032,11 @@ Thread::Thread() #endif m_pAllLoggedTypes = NULL; + +#ifdef FEATURE_PERFTRACING + m_pEventPipeBufferList = NULL; + m_eventWriteInProgress = false; +#endif // FEATURE_PERFTRACING m_HijackReturnKind = RT_Illegal; } diff --git a/src/vm/threads.h b/src/vm/threads.h index 34fca24..fbff1b9 100644 --- a/src/vm/threads.h +++ b/src/vm/threads.h @@ -185,6 +185,10 @@ typedef DPTR(PTR_ThreadLocalBlock) PTR_PTR_ThreadLocalBlock; #include "interoputil.h" #include "eventtrace.h" +#ifdef FEATURE_PERFTRACING +class EventPipeBufferList; +#endif // FEATURE_PERFTRACING + #ifdef CROSSGEN_COMPILE #include "asmconstants.h" @@ -5334,6 +5338,40 @@ public: m_pAllLoggedTypes = pAllLoggedTypes; } +#ifdef FEATURE_PERFTRACING +private: + // The object that contains the list write buffers used by this thread. + Volatile m_pEventPipeBufferList; + + // Whether or not the thread is currently writing an event. + Volatile m_eventWriteInProgress; + +public: + EventPipeBufferList* GetEventPipeBufferList() + { + LIMITED_METHOD_CONTRACT; + return m_pEventPipeBufferList; + } + + void SetEventPipeBufferList(EventPipeBufferList *pList) + { + LIMITED_METHOD_CONTRACT; + m_pEventPipeBufferList = pList; + } + + bool GetEventWriteInProgress() const + { + LIMITED_METHOD_CONTRACT; + return m_eventWriteInProgress; + } + + void SetEventWriteInProgress(bool value) + { + LIMITED_METHOD_CONTRACT; + m_eventWriteInProgress = value; + } +#endif // FEATURE_PERFTRACING + #ifdef FEATURE_HIJACK private: