summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBrian Robbins <brianrob@microsoft.com>2017-05-12 10:51:31 -0700
committerGitHub <noreply@github.com>2017-05-12 10:51:31 -0700
commitfca97d0ca72524b3bdd61817f7a172dd47d53287 (patch)
tree2cd8ca575969c94f487e7e9d175099b31fcf6ff6 /src
parentf70698458849e3541dc96fac8d6c0d6b52ccf048 (diff)
downloadcoreclr-fca97d0ca72524b3bdd61817f7a172dd47d53287.tar.gz
coreclr-fca97d0ca72524b3bdd61817f7a172dd47d53287.tar.bz2
coreclr-fca97d0ca72524b3bdd61817f7a172dd47d53287.zip
EventPipe Circular Buffer Support and Ability to Start/Stop Tracing (#11507)
Diffstat (limited to 'src')
-rw-r--r--src/mscorlib/System.Private.CoreLib.csproj3
-rw-r--r--src/mscorlib/src/System/Diagnostics/Eventing/EventPipe.cs157
-rw-r--r--src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs20
-rw-r--r--src/vm/CMakeLists.txt2
-rw-r--r--src/vm/ecalllist.h2
-rw-r--r--src/vm/eventpipe.cpp218
-rw-r--r--src/vm/eventpipe.h71
-rw-r--r--src/vm/eventpipebuffer.cpp275
-rw-r--r--src/vm/eventpipebuffer.h109
-rw-r--r--src/vm/eventpipebuffermanager.cpp798
-rw-r--r--src/vm/eventpipebuffermanager.h161
-rw-r--r--src/vm/eventpipeconfiguration.cpp267
-rw-r--r--src/vm/eventpipeconfiguration.h92
-rw-r--r--src/vm/eventpipeevent.h2
-rw-r--r--src/vm/eventpipeeventinstance.cpp45
-rw-r--r--src/vm/eventpipeeventinstance.h24
-rw-r--r--src/vm/eventpipefile.cpp28
-rw-r--r--src/vm/eventpipefile.h12
-rw-r--r--src/vm/eventpipejsonfile.cpp2
-rw-r--r--src/vm/eventpipejsonfile.h2
-rw-r--r--src/vm/eventpipeprovider.cpp12
-rw-r--r--src/vm/eventpipeprovider.h14
-rw-r--r--src/vm/sampleprofiler.cpp14
-rw-r--r--src/vm/sampleprofiler.h1
-rw-r--r--src/vm/threads.cpp29
-rw-r--r--src/vm/threads.h38
26 files changed, 2286 insertions, 112 deletions
diff --git a/src/mscorlib/System.Private.CoreLib.csproj b/src/mscorlib/System.Private.CoreLib.csproj
index 3373097616..75d5ab81c4 100644
--- a/src/mscorlib/System.Private.CoreLib.csproj
+++ b/src/mscorlib/System.Private.CoreLib.csproj
@@ -569,6 +569,7 @@
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventSource_CoreCLR.cs" />
<Compile Condition="'$(FeatureXplatEventSource)' == 'true'" Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\XplatEventLogger.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\FrameworkEventSource.cs" />
+ <Compile Condition="'$(FeaturePerfTracing)' == 'true'" Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipe.cs" />
<Compile Condition="'$(FeaturePerfTracing)' == 'true'" Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeEventProvider.cs" />
</ItemGroup>
<ItemGroup>
@@ -734,4 +735,4 @@
<Win32Resource Condition="'$(GenerateNativeVersionInfo)'=='true'">$(IntermediateOutputPath)\System.Private.CoreLib.res</Win32Resource>
</PropertyGroup>
<Import Project="GenerateCompilerResponseFile.targets" />
-</Project> \ No newline at end of file
+</Project>
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 0000000000..4c6778fa23
--- /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<EventPipeProviderConfiguration> 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<EventPipeProviderConfiguration>();
+ }
+
+ 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 5917ecc89c..42eb42ae2d 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 556eb0e93a..c610d3c7a8 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 ef82daab95..c51b4a8eae 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 bed4cfdbc4..5660a56d34 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 d5b85f7e3a..626c4dff53 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 0000000000..e0e9610800
--- /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 0000000000..97b858d018
--- /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 0000000000..4e6b6b9e2b
--- /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<SListElem<EventPipeBufferList*>>();
+ 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<EventPipeBufferList*>(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<EventPipeBufferList*> *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<EventPipeBufferList*> *pElem = m_pPerThreadBufferList->GetHead();
+ while(pElem != NULL)
+ {
+ EventPipeBufferList *pBufferList = pElem->GetValue();
+
+ // Peek the next event out of the list.
+ EventPipeBuffer *pContainingBuffer = NULL;
+ EventPipeEventInstance *pNext = pBufferList->PeekNextEvent(stopTimeStamp, &pContainingBuffer);
+ if(pNext != NULL)
+ {
+ // If it's the oldest event we've seen, then save it.
+ if((pOldestInstance == NULL) ||
+ (pOldestInstance->GetTimeStamp().QuadPart > pNext->GetTimeStamp().QuadPart))
+ {
+ pOldestInstance = pNext;
+ pOldestContainingBuffer = pContainingBuffer;
+ pOldestContainingList = pBufferList;
+ }
+ }
+
+ pElem = m_pPerThreadBufferList->GetNext(pElem);
+ }
+
+ if(pOldestInstance == NULL)
+ {
+ // We're done. There are no more events.
+ 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<EventPipeBufferList*> *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<EventPipeBufferList*> *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<EventPipeBufferList*> *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 0000000000..74783d26e8
--- /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<SListElem<EventPipeBufferList*>> *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<LONG> 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<bool> 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 01286850a2..73fa963a45 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<SListElem<EventPipeProvider*>>();
}
@@ -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<EventPipeProvider*>(&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<unsigned int>(numProviders));
+ m_enabled = true;
+
SListElem<EventPipeProvider*> *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; i<m_numProviders; i++)
+ {
+ m_pProviders[i].Set(
+ pConfigs[i].GetProviderName(),
+ pConfigs[i].GetKeywords(),
+ (EventPipeEventLevel)pConfigs[i].GetLevel());
+ }
+}
+
+EventPipeEnabledProviderList::~EventPipeEnabledProviderList()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if(m_pProviders != NULL)
+ {
+ delete[] m_pProviders;
+ m_pProviders = NULL;
+ }
+ if(m_pCatchAllProvider != NULL)
+ {
+ delete(m_pCatchAllProvider);
+ m_pCatchAllProvider = NULL;
+ }
+}
+
+EventPipeEnabledProvider* EventPipeEnabledProviderList::GetEnabledProvider(
+ EventPipeProvider *pProvider)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // If tracing was enabled on start-up, all events should be on (this is a diagnostic config).
+ if(m_pCatchAllProvider != NULL)
+ {
+ return m_pCatchAllProvider;
+ }
+
+ if(m_pProviders == NULL)
+ {
+ return NULL;
+ }
+
+ // TEMPORARY: Convert the provider GUID to a string.
+ const unsigned int guidSize = 39;
+ WCHAR wszProviderID[guidSize];
+ if(!StringFromGUID2(pProvider->GetProviderID(), 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; i<m_numProviders; i++)
+ {
+ EventPipeEnabledProvider *pCandidate = &m_pProviders[i];
+ if(pCandidate != NULL)
+ {
+ if(wcscmp(providerName, pCandidate->GetProviderName()) == 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 a2377757d4..baa9b3bd23 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<bool> 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<SListElem<EventPipeProvider*>> *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 9e42615ed9..3076617f8a 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<bool> 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 2bf500be70..7877b797b1 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 84ad566489..7e0ea47ffd 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 895f732f51..f574814586 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<EventPipeEvent*, StreamLabel>();
+#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 1fbb4c0b79..2f6853545d 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<EventPipeEvent*, StreamLabel> *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 ea2dd29fe0..f76959053c 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 2b836d2c67..7686db7752 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 362c37a50f..be87fb4db6 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<SListElem<EventPipeEvent*>>();
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<EventPipeEvent*>(&event));
+ event.RefreshState();
}
void EventPipeProvider::InvokeCallback()
diff --git a/src/vm/eventpipeprovider.h b/src/vm/eventpipeprovider.h
index d1ce1584d7..7aaa8e4d7f 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 6a6a23a03b..7c6429a211 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 5ad388d8ff..19deb08e9d 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 c36232ecb5..df8916c1f9 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 34fca24c4f..fbff1b9bdd 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<EventPipeBufferList*> m_pEventPipeBufferList;
+
+ // Whether or not the thread is currently writing an event.
+ Volatile<bool> 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: