summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clrdefinitions.cmake1
-rw-r--r--src/inc/CrstTypes.def6
-rw-r--r--src/inc/clrconfigvalues.h5
-rw-r--r--src/inc/crsttypes.h260
-rw-r--r--src/pal/inc/pal.h6
-rw-r--r--src/pal/src/misc/time.cpp32
-rw-r--r--src/vm/CMakeLists.txt3
-rw-r--r--src/vm/ceemain.cpp14
-rw-r--r--src/vm/eventpipe.cpp234
-rw-r--r--src/vm/eventpipe.h154
-rw-r--r--src/vm/eventpipejsonfile.cpp131
-rw-r--r--src/vm/eventpipejsonfile.h40
-rw-r--r--src/vm/sampleprofiler.cpp155
-rw-r--r--src/vm/sampleprofiler.h44
14 files changed, 955 insertions, 130 deletions
diff --git a/clrdefinitions.cmake b/clrdefinitions.cmake
index 9ca67decaf..c4bfb8ea39 100644
--- a/clrdefinitions.cmake
+++ b/clrdefinitions.cmake
@@ -122,6 +122,7 @@ endif(CLR_CMAKE_PLATFORM_UNIX AND (NOT CLR_CMAKE_PLATFORM_ANDROID))
if(FEATURE_EVENT_TRACE)
add_definitions(-DFEATURE_EVENT_TRACE=1)
endif(FEATURE_EVENT_TRACE)
+add_definitions(-DFEATURE_PERFTRACING)
if(CLR_CMAKE_PLATFORM_UNIX)
add_definitions(-DFEATURE_EVENTSOURCE_XPLAT=1)
endif(CLR_CMAKE_PLATFORM_UNIX)
diff --git a/src/inc/CrstTypes.def b/src/inc/CrstTypes.def
index bb6e710647..227f986a85 100644
--- a/src/inc/CrstTypes.def
+++ b/src/inc/CrstTypes.def
@@ -778,4 +778,8 @@ End
Crst InlineTrackingMap
AcquiredBefore IbcProfile
-End \ No newline at end of file
+End
+
+Crst EventPipe
+ AcquiredBefore ThreadIdDispenser ThreadStore
+End
diff --git a/src/inc/clrconfigvalues.h b/src/inc/clrconfigvalues.h
index 0a285a173e..a0c24567d7 100644
--- a/src/inc/clrconfigvalues.h
+++ b/src/inc/clrconfigvalues.h
@@ -1054,6 +1054,11 @@ RETAIL_CONFIG_STRING_INFO(INTERNAL_LocalWinMDPath, W("LocalWinMDPath"), "Additio
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_AllowDComReflection, W("AllowDComReflection"), 0, "Allows out of process DCOM clients to marshal blocked reflection types.")
//
+// Performance Tracing
+//
+RETAIL_CONFIG_DWORD_INFO(INTERNAL_PerformanceTracing, W("PerformanceTracing"), 0, "Enable/disable performance tracing. Non-zero values enable tracing.")
+
+//
// Unknown
//
//---------------------------------------------------------------------------------------
diff --git a/src/inc/crsttypes.h b/src/inc/crsttypes.h
index 8c702fa553..b4f6f49e64 100644
--- a/src/inc/crsttypes.h
+++ b/src/inc/crsttypes.h
@@ -56,134 +56,135 @@ enum CrstType
CrstDynamicMT = 39,
CrstDynLinkZapItems = 40,
CrstEtwTypeLogHash = 41,
- CrstEventStore = 42,
- CrstException = 43,
- CrstExecuteManLock = 44,
- CrstExecuteManRangeLock = 45,
- CrstFCall = 46,
- CrstFriendAccessCache = 47,
- CrstFuncPtrStubs = 48,
- CrstFusionAppCtx = 49,
- CrstFusionAssemblyDownload = 50,
- CrstFusionBindContext = 51,
- CrstFusionBindResult = 52,
- CrstFusionClb = 53,
- CrstFusionClosure = 54,
- CrstFusionClosureGraph = 55,
- CrstFusionConfigSettings = 56,
- CrstFusionDownload = 57,
- CrstFusionIsoLibInit = 58,
- CrstFusionLoadContext = 59,
- CrstFusionLog = 60,
- CrstFusionNgenIndex = 61,
- CrstFusionNgenIndexPool = 62,
- CrstFusionPcyCache = 63,
- CrstFusionPolicyConfigPool = 64,
- CrstFusionSingleUse = 65,
- CrstFusionWarningLog = 66,
- CrstGCMemoryPressure = 67,
- CrstGlobalStrLiteralMap = 68,
- CrstHandleTable = 69,
- CrstHostAssemblyMap = 70,
- CrstHostAssemblyMapAdd = 71,
- CrstIbcProfile = 72,
- CrstIJWFixupData = 73,
- CrstIJWHash = 74,
- CrstILFingerprintCache = 75,
- CrstILStubGen = 76,
- CrstInlineTrackingMap = 77,
- CrstInstMethodHashTable = 78,
- CrstInterfaceVTableMap = 79,
- CrstInterop = 80,
- CrstInteropData = 81,
- CrstIOThreadpoolWorker = 82,
- CrstIsJMCMethod = 83,
- CrstISymUnmanagedReader = 84,
- CrstJit = 85,
- CrstJitGenericHandleCache = 86,
- CrstJitPerf = 87,
- CrstJumpStubCache = 88,
- CrstLeafLock = 89,
- CrstListLock = 90,
- CrstLoaderAllocator = 91,
- CrstLoaderAllocatorReferences = 92,
- CrstLoaderHeap = 93,
- CrstMda = 94,
- CrstMetadataTracker = 95,
- CrstModIntPairList = 96,
- CrstModule = 97,
- CrstModuleFixup = 98,
- CrstModuleLookupTable = 99,
- CrstMulticoreJitHash = 100,
- CrstMulticoreJitManager = 101,
- CrstMUThunkHash = 102,
- CrstNativeBinderInit = 103,
- CrstNativeImageCache = 104,
- CrstNls = 105,
- CrstObjectList = 106,
- CrstOnEventManager = 107,
- CrstPatchEntryPoint = 108,
- CrstPEFileSecurityManager = 109,
- CrstPEImage = 110,
- CrstPEImagePDBStream = 111,
- CrstPendingTypeLoadEntry = 112,
- CrstPinHandle = 113,
- CrstPinnedByrefValidation = 114,
- CrstProfilerGCRefDataFreeList = 115,
- CrstProfilingAPIStatus = 116,
- CrstPublisherCertificate = 117,
- CrstRCWCache = 118,
- CrstRCWCleanupList = 119,
- CrstRCWRefCache = 120,
- CrstReDacl = 121,
- CrstReflection = 122,
- CrstReJITDomainTable = 123,
- CrstReJITGlobalRequest = 124,
- CrstReJITSharedDomainTable = 125,
- CrstRemoting = 126,
- CrstRetThunkCache = 127,
- CrstRWLock = 128,
- CrstSavedExceptionInfo = 129,
- CrstSaveModuleProfileData = 130,
- CrstSecurityPolicyCache = 131,
- CrstSecurityPolicyInit = 132,
- CrstSecurityStackwalkCache = 133,
- CrstSharedAssemblyCreate = 134,
- CrstSharedBaseDomain = 135,
- CrstSigConvert = 136,
- CrstSingleUseLock = 137,
- CrstSpecialStatics = 138,
- CrstSqmManager = 139,
- CrstStackSampler = 140,
- CrstStressLog = 141,
- CrstStrongName = 142,
- CrstStubCache = 143,
- CrstStubDispatchCache = 144,
- CrstStubUnwindInfoHeapSegments = 145,
- CrstSyncBlockCache = 146,
- CrstSyncHashLock = 147,
- CrstSystemBaseDomain = 148,
- CrstSystemDomain = 149,
- CrstSystemDomainDelayedUnloadList = 150,
- CrstThreadIdDispenser = 151,
- CrstThreadpoolEventCache = 152,
- CrstThreadpoolTimerQueue = 153,
- CrstThreadpoolWaitThreads = 154,
- CrstThreadpoolWorker = 155,
- CrstThreadStaticDataHashTable = 156,
- CrstThreadStore = 157,
- CrstTPMethodTable = 158,
- CrstTypeEquivalenceMap = 159,
- CrstTypeIDMap = 160,
- CrstUMEntryThunkCache = 161,
- CrstUMThunkHash = 162,
- CrstUniqueStack = 163,
- CrstUnresolvedClassLock = 164,
- CrstUnwindInfoTableLock = 165,
- CrstVSDIndirectionCellLock = 166,
- CrstWinRTFactoryCache = 167,
- CrstWrapperTemplate = 168,
- kNumberOfCrstTypes = 169
+ CrstEventPipe = 42,
+ CrstEventStore = 43,
+ CrstException = 44,
+ CrstExecuteManLock = 45,
+ CrstExecuteManRangeLock = 46,
+ CrstFCall = 47,
+ CrstFriendAccessCache = 48,
+ CrstFuncPtrStubs = 49,
+ CrstFusionAppCtx = 50,
+ CrstFusionAssemblyDownload = 51,
+ CrstFusionBindContext = 52,
+ CrstFusionBindResult = 53,
+ CrstFusionClb = 54,
+ CrstFusionClosure = 55,
+ CrstFusionClosureGraph = 56,
+ CrstFusionConfigSettings = 57,
+ CrstFusionDownload = 58,
+ CrstFusionIsoLibInit = 59,
+ CrstFusionLoadContext = 60,
+ CrstFusionLog = 61,
+ CrstFusionNgenIndex = 62,
+ CrstFusionNgenIndexPool = 63,
+ CrstFusionPcyCache = 64,
+ CrstFusionPolicyConfigPool = 65,
+ CrstFusionSingleUse = 66,
+ CrstFusionWarningLog = 67,
+ CrstGCMemoryPressure = 68,
+ CrstGlobalStrLiteralMap = 69,
+ CrstHandleTable = 70,
+ CrstHostAssemblyMap = 71,
+ CrstHostAssemblyMapAdd = 72,
+ CrstIbcProfile = 73,
+ CrstIJWFixupData = 74,
+ CrstIJWHash = 75,
+ CrstILFingerprintCache = 76,
+ CrstILStubGen = 77,
+ CrstInlineTrackingMap = 78,
+ CrstInstMethodHashTable = 79,
+ CrstInterfaceVTableMap = 80,
+ CrstInterop = 81,
+ CrstInteropData = 82,
+ CrstIOThreadpoolWorker = 83,
+ CrstIsJMCMethod = 84,
+ CrstISymUnmanagedReader = 85,
+ CrstJit = 86,
+ CrstJitGenericHandleCache = 87,
+ CrstJitPerf = 88,
+ CrstJumpStubCache = 89,
+ CrstLeafLock = 90,
+ CrstListLock = 91,
+ CrstLoaderAllocator = 92,
+ CrstLoaderAllocatorReferences = 93,
+ CrstLoaderHeap = 94,
+ CrstMda = 95,
+ CrstMetadataTracker = 96,
+ CrstModIntPairList = 97,
+ CrstModule = 98,
+ CrstModuleFixup = 99,
+ CrstModuleLookupTable = 100,
+ CrstMulticoreJitHash = 101,
+ CrstMulticoreJitManager = 102,
+ CrstMUThunkHash = 103,
+ CrstNativeBinderInit = 104,
+ CrstNativeImageCache = 105,
+ CrstNls = 106,
+ CrstObjectList = 107,
+ CrstOnEventManager = 108,
+ CrstPatchEntryPoint = 109,
+ CrstPEFileSecurityManager = 110,
+ CrstPEImage = 111,
+ CrstPEImagePDBStream = 112,
+ CrstPendingTypeLoadEntry = 113,
+ CrstPinHandle = 114,
+ CrstPinnedByrefValidation = 115,
+ CrstProfilerGCRefDataFreeList = 116,
+ CrstProfilingAPIStatus = 117,
+ CrstPublisherCertificate = 118,
+ CrstRCWCache = 119,
+ CrstRCWCleanupList = 120,
+ CrstRCWRefCache = 121,
+ CrstReDacl = 122,
+ CrstReflection = 123,
+ CrstReJITDomainTable = 124,
+ CrstReJITGlobalRequest = 125,
+ CrstReJITSharedDomainTable = 126,
+ CrstRemoting = 127,
+ CrstRetThunkCache = 128,
+ CrstRWLock = 129,
+ CrstSavedExceptionInfo = 130,
+ CrstSaveModuleProfileData = 131,
+ CrstSecurityPolicyCache = 132,
+ CrstSecurityPolicyInit = 133,
+ CrstSecurityStackwalkCache = 134,
+ CrstSharedAssemblyCreate = 135,
+ CrstSharedBaseDomain = 136,
+ CrstSigConvert = 137,
+ CrstSingleUseLock = 138,
+ CrstSpecialStatics = 139,
+ CrstSqmManager = 140,
+ CrstStackSampler = 141,
+ CrstStressLog = 142,
+ CrstStrongName = 143,
+ CrstStubCache = 144,
+ CrstStubDispatchCache = 145,
+ CrstStubUnwindInfoHeapSegments = 146,
+ CrstSyncBlockCache = 147,
+ CrstSyncHashLock = 148,
+ CrstSystemBaseDomain = 149,
+ CrstSystemDomain = 150,
+ CrstSystemDomainDelayedUnloadList = 151,
+ CrstThreadIdDispenser = 152,
+ CrstThreadpoolEventCache = 153,
+ CrstThreadpoolTimerQueue = 154,
+ CrstThreadpoolWaitThreads = 155,
+ CrstThreadpoolWorker = 156,
+ CrstThreadStaticDataHashTable = 157,
+ CrstThreadStore = 158,
+ CrstTPMethodTable = 159,
+ CrstTypeEquivalenceMap = 160,
+ CrstTypeIDMap = 161,
+ CrstUMEntryThunkCache = 162,
+ CrstUMThunkHash = 163,
+ CrstUniqueStack = 164,
+ CrstUnresolvedClassLock = 165,
+ CrstUnwindInfoTableLock = 166,
+ CrstVSDIndirectionCellLock = 167,
+ CrstWinRTFactoryCache = 168,
+ CrstWrapperTemplate = 169,
+ kNumberOfCrstTypes = 170
};
#endif // __CRST_TYPES_INCLUDED
@@ -236,6 +237,7 @@ int g_rgCrstLevelMap[] =
3, // CrstDynamicMT
3, // CrstDynLinkZapItems
7, // CrstEtwTypeLogHash
+ 11, // CrstEventPipe
0, // CrstEventStore
0, // CrstException
7, // CrstExecuteManLock
@@ -410,6 +412,7 @@ LPCSTR g_rgCrstNameMap[] =
"CrstDynamicMT",
"CrstDynLinkZapItems",
"CrstEtwTypeLogHash",
+ "CrstEventPipe",
"CrstEventStore",
"CrstException",
"CrstExecuteManLock",
@@ -557,3 +560,4 @@ inline static LPCSTR GetCrstName(CrstType crstType)
}
#endif // defined(__IN_CRST_CPP) && defined(_DEBUG)
+
diff --git a/src/pal/inc/pal.h b/src/pal/inc/pal.h
index 589eb54831..8430ea9899 100644
--- a/src/pal/inc/pal.h
+++ b/src/pal/inc/pal.h
@@ -4137,6 +4137,12 @@ QueryThreadCycleTime(
IN HANDLE ThreadHandle,
OUT PULONG64 CycleTime);
+PALIMPORT
+INT
+PALAPI
+PAL_nanosleep(
+ IN long timeInNs);
+
#ifndef FEATURE_PAL_SXS
typedef LONG (PALAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
diff --git a/src/pal/src/misc/time.cpp b/src/pal/src/misc/time.cpp
index 918f92a90f..d16fb587ba 100644
--- a/src/pal/src/misc/time.cpp
+++ b/src/pal/src/misc/time.cpp
@@ -394,3 +394,35 @@ EXIT:
return retval;
}
+/*++
+Function:
+ PAL_nanosleep
+
+Sleeps for the time specified in timeInNs.
+Returns 0 on successful completion of the operation.
+--*/
+PALAPI
+INT
+PAL_nanosleep(
+ IN long timeInNs
+ )
+{
+ struct timespec req;
+ struct timespec rem;
+ int result;
+
+ req.tv_sec = 0;
+ req.tv_nsec = timeInNs;
+
+ do
+ {
+ // Sleep for the requested time.
+ result = nanosleep(&req, &rem);
+
+ // Save the remaining time (used if the loop runs another iteration).
+ req = rem;
+ }
+ while(result == -1 && errno == EINTR);
+
+ return result;
+}
diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt
index 861d68c9c9..26fcacf4af 100644
--- a/src/vm/CMakeLists.txt
+++ b/src/vm/CMakeLists.txt
@@ -164,6 +164,8 @@ set(VM_SOURCES_WKS
eemessagebox.cpp
eepolicy.cpp
eetoprofinterfaceimpl.cpp
+ eventpipe.cpp
+ eventpipejsonfile.cpp
eventstore.cpp
fcall.cpp
fieldmarshaler.cpp
@@ -211,6 +213,7 @@ set(VM_SOURCES_WKS
reflectioninvocation.cpp
runtimehandles.cpp
safehandle.cpp
+ sampleprofiler.cpp
security.cpp
securityattributes.cpp
securitydeclarative.cpp
diff --git a/src/vm/ceemain.cpp b/src/vm/ceemain.cpp
index d5d09626a2..dd8438e7db 100644
--- a/src/vm/ceemain.cpp
+++ b/src/vm/ceemain.cpp
@@ -229,6 +229,8 @@
#include "perfmap.h"
#endif
+#include "eventpipe.h"
+
#ifndef FEATURE_PAL
// Included for referencing __security_cookie
#include "process.h"
@@ -1032,7 +1034,12 @@ void EEStartupHelper(COINITIEE fFlags)
SystemDomain::System()->DefaultDomain()));
SystemDomain::System()->PublishAppDomainAndInformDebugger(SystemDomain::System()->DefaultDomain());
#endif
-
+
+#ifdef FEATURE_PERFTRACING
+ // Initialize the event pipe and start it if requested.
+ EventPipe::Initialize();
+ EventPipe::EnableOnStartup();
+#endif // FEATURE_PERFTRACING
#endif // CROSSGEN_COMPILE
@@ -1701,6 +1708,11 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading)
PerfMap::Destroy();
#endif
+#ifdef FEATURE_PERFTRACING
+ // Shutdown the event pipe.
+ EventPipe::Shutdown();
+#endif // FEATURE_PERFTRACING
+
#ifdef FEATURE_PREJIT
{
// If we're doing basic block profiling, we need to write the log files to disk.
diff --git a/src/vm/eventpipe.cpp b/src/vm/eventpipe.cpp
new file mode 100644
index 0000000000..98d382ea17
--- /dev/null
+++ b/src/vm/eventpipe.cpp
@@ -0,0 +1,234 @@
+// 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 "eventpipe.h"
+#include "eventpipejsonfile.h"
+#include "sampleprofiler.h"
+
+CrstStatic EventPipe::s_initCrst;
+bool EventPipe::s_tracingInitialized = false;
+bool EventPipe::s_tracingEnabled = false;
+EventPipeJsonFile* EventPipe::s_pJsonFile = NULL;
+
+void EventPipe::Initialize()
+{
+ STANDARD_VM_CONTRACT;
+
+ s_tracingInitialized = s_initCrst.InitNoThrow(
+ CrstEventPipe,
+ (CrstFlags)(CRST_TAKEN_DURING_SHUTDOWN));
+}
+
+void EventPipe::EnableOnStartup()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // Test COMPLUS variable to enable tracing at start-up.
+ if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PerformanceTracing) != 0)
+ {
+ Enable();
+ }
+}
+
+void EventPipe::Shutdown()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ Disable();
+}
+
+void EventPipe::Enable()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if(!s_tracingInitialized)
+ {
+ return;
+ }
+
+ // Take the lock and enable tracing.
+ CrstHolder _crst(&s_initCrst);
+ s_tracingEnabled = true;
+ if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PerformanceTracing) == 2)
+ {
+ // File placed in current working directory.
+ SString outputFilePath;
+ outputFilePath.Printf("Process-%d.PerfView.json", GetCurrentProcessId());
+ s_pJsonFile = new EventPipeJsonFile(outputFilePath);
+ }
+
+ SampleProfiler::Enable();
+}
+
+void EventPipe::Disable()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ CrstHolder _crst(&s_initCrst);
+ s_tracingEnabled = false;
+ SampleProfiler::Disable();
+
+ if(s_pJsonFile != NULL)
+ {
+ delete(s_pJsonFile);
+ s_pJsonFile = NULL;
+ }
+}
+
+bool EventPipe::EventEnabled(GUID& providerID, INT64 keyword)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // TODO: Implement filtering.
+ return false;
+}
+
+void EventPipe::WriteEvent(GUID& providerID, INT64 eventID, BYTE *pData, size_t length, bool sampleStack)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ StackContents stackContents;
+ bool stackWalkSucceeded;
+
+ if(sampleStack)
+ {
+ stackWalkSucceeded = WalkManagedStackForCurrentThread(stackContents);
+ }
+
+ // TODO: Write the event.
+}
+
+void EventPipe::WriteSampleProfileEvent(Thread *pThread, StackContents &stackContents)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(pThread != NULL);
+ }
+ CONTRACTL_END;
+
+ EX_TRY
+ {
+ if(s_pJsonFile != NULL)
+ {
+ CommonEventFields eventFields;
+ QueryPerformanceCounter(&eventFields.TimeStamp);
+ eventFields.ThreadID = pThread->GetOSThreadId();
+
+ static SString message(W("THREAD_TIME"));
+ s_pJsonFile->WriteEvent(eventFields, message, stackContents);
+ }
+ }
+ EX_CATCH{} EX_END_CATCH(SwallowAllExceptions);
+}
+
+bool EventPipe::WalkManagedStackForCurrentThread(StackContents &stackContents)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ Thread *pThread = GetThread();
+ _ASSERTE(pThread != NULL);
+ return WalkManagedStackForThread(pThread, stackContents);
+}
+
+bool EventPipe::WalkManagedStackForThread(Thread *pThread, StackContents &stackContents)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(pThread != NULL);
+ }
+ CONTRACTL_END;
+
+ stackContents.Reset();
+
+ StackWalkAction swaRet = pThread->StackWalkFrames(
+ (PSTACKWALKFRAMESCALLBACK) &StackWalkCallback,
+ &stackContents,
+ ALLOW_ASYNC_STACK_WALK | FUNCTIONSONLY | HANDLESKIPPEDFRAMES);
+
+ return ((swaRet == SWA_DONE) || (swaRet == SWA_CONTINUE));
+}
+
+StackWalkAction EventPipe::StackWalkCallback(CrawlFrame *pCf, StackContents *pData)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ PRECONDITION(pCf != NULL);
+ PRECONDITION(pData != NULL);
+ }
+ CONTRACTL_END;
+
+ // Get the IP.
+ UINT_PTR controlPC = (UINT_PTR)pCf->GetRegisterSet()->ControlPC;
+ if(controlPC == 0)
+ {
+ if(pData->GetLength() == 0)
+ {
+ // This happens for pinvoke stubs on the top of the stack.
+ return SWA_CONTINUE;
+ }
+ }
+
+ _ASSERTE(controlPC != 0);
+
+ // Add the IP to the captured stack.
+ pData->Append(
+ controlPC,
+ pCf->GetFunction()
+ );
+
+ // Continue the stack walk.
+ return SWA_CONTINUE;
+}
diff --git a/src/vm/eventpipe.h b/src/vm/eventpipe.h
new file mode 100644
index 0000000000..2978412325
--- /dev/null
+++ b/src/vm/eventpipe.h
@@ -0,0 +1,154 @@
+// 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_H__
+#define __EVENTPIPE_H__
+
+#include "common.h"
+
+class EventPipeJsonFile;
+
+// The data fields common to every event.
+struct CommonEventFields
+{
+ // Timestamp generated by QueryPerformanceCounter.
+ LARGE_INTEGER TimeStamp;
+
+ // Thread ID.
+ DWORD ThreadID;
+};
+
+class StackContents
+{
+private:
+
+ const static unsigned int MAX_STACK_DEPTH = 100;
+
+ // Array of IP values from a stack crawl.
+ // Top of stack is at index 0.
+ UINT_PTR m_stackFrames[MAX_STACK_DEPTH];
+
+ // Parallel array of MethodDesc pointers.
+ // Used for debug-only stack printing.
+ MethodDesc* m_methods[MAX_STACK_DEPTH];
+
+ // The next available slot in StackFrames.
+ unsigned int m_nextAvailableFrame;
+
+public:
+
+ StackContents()
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ Reset();
+ }
+
+ void Reset()
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ m_nextAvailableFrame = 0;
+ }
+
+ bool IsEmpty()
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return (m_nextAvailableFrame == 0);
+ }
+
+ unsigned int GetLength()
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return m_nextAvailableFrame;
+ }
+
+ UINT_PTR GetIP(unsigned int frameIndex)
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(frameIndex < MAX_STACK_DEPTH);
+
+ if (frameIndex >= MAX_STACK_DEPTH)
+ {
+ return 0;
+ }
+
+ return m_stackFrames[frameIndex];
+ }
+
+ MethodDesc* GetMethod(unsigned int frameIndex)
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(frameIndex < MAX_STACK_DEPTH);
+
+ if (frameIndex >= MAX_STACK_DEPTH)
+ {
+ return NULL;
+ }
+
+ return m_methods[frameIndex];
+ }
+
+ void Append(UINT_PTR controlPC, MethodDesc *pMethod)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ if(m_nextAvailableFrame < MAX_STACK_DEPTH)
+ {
+ m_stackFrames[m_nextAvailableFrame] = controlPC;
+ m_methods[m_nextAvailableFrame] = pMethod;
+ m_nextAvailableFrame++;
+ }
+ }
+};
+
+class EventPipe
+{
+ public:
+
+ // Initialize the event pipe.
+ static void Initialize();
+
+ // Shutdown the event pipe.
+ static void Shutdown();
+
+ // Enable tracing from the start-up path based on COMPLUS variable.
+ static void EnableOnStartup();
+
+ // Enable tracing via the event pipe.
+ static void Enable();
+
+ // Disable tracing via the event pipe.
+ static void Disable();
+
+ // Determine whether or not the specified provider/keyword combination is enabled.
+ static bool EventEnabled(GUID& providerID, INT64 keyword);
+
+ // Write out an event. The event is identified by the providerID/eventID pair.
+ // Data is written as a serialized blob matching the ETW serialization conventions.
+ static void WriteEvent(GUID& providerID, INT64 eventID, BYTE *pData, size_t length, bool sampleStack);
+
+ // Write out a sample profile event with the specified stack.
+ static void WriteSampleProfileEvent(Thread *pThread, StackContents &stackContents);
+
+ // Get the managed call stack for the current thread.
+ static bool WalkManagedStackForCurrentThread(StackContents &stackContents);
+
+ // Get the managed call stack for the specified thread.
+ static bool WalkManagedStackForThread(Thread *pThread, StackContents &stackContents);
+
+ private:
+
+ // Callback function for the stack walker. For each frame walked, this callback is invoked.
+ static StackWalkAction StackWalkCallback(CrawlFrame *pCf, StackContents *pData);
+
+ static CrstStatic s_initCrst;
+ static bool s_tracingInitialized;
+ static bool s_tracingEnabled;
+ static EventPipeJsonFile *s_pJsonFile;
+};
+
+#endif // __EVENTPIPE_H__
diff --git a/src/vm/eventpipejsonfile.cpp b/src/vm/eventpipejsonfile.cpp
new file mode 100644
index 0000000000..6353c917e7
--- /dev/null
+++ b/src/vm/eventpipejsonfile.cpp
@@ -0,0 +1,131 @@
+// 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 "eventpipejsonfile.h"
+
+EventPipeJsonFile::EventPipeJsonFile(SString &outFilePath)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ m_pFileStream = new CFileStream();
+ if(FAILED(m_pFileStream->OpenForWrite(outFilePath)))
+ {
+ delete(m_pFileStream);
+ m_pFileStream = NULL;
+ return;
+ }
+
+ QueryPerformanceCounter(&m_fileOpenTimeStamp);
+
+ SString fileStart(W("{\n\"StackSource\" : {\n\"Samples\" : [\n"));
+ Write(fileStart);
+}
+
+EventPipeJsonFile::~EventPipeJsonFile()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if(m_pFileStream != NULL)
+ {
+ if(!m_writeErrorEncountered)
+ {
+ SString closingString(W("]}}"));
+ Write(closingString);
+ }
+
+ delete(m_pFileStream);
+ m_pFileStream = NULL;
+ }
+}
+
+void EventPipeJsonFile::WriteEvent(CommonEventFields &commonFields, SString &message, StackContents &stackContents)
+{
+ STANDARD_VM_CONTRACT;
+
+ if(m_pFileStream == NULL || m_writeErrorEncountered)
+ {
+ return;
+ }
+
+ // Format the call stack.
+ SString strCallStack;
+ FormatCallStack(stackContents, strCallStack);
+
+ // Convert the timestamp from a QPC value to a trace-relative timestamp.
+ double millisecondsSinceTraceStart = 0.0;
+ if(commonFields.TimeStamp.QuadPart != m_fileOpenTimeStamp.QuadPart)
+ {
+ LARGE_INTEGER elapsedNanoseconds;
+ elapsedNanoseconds.QuadPart = commonFields.TimeStamp.QuadPart - m_fileOpenTimeStamp.QuadPart;
+ millisecondsSinceTraceStart = elapsedNanoseconds.QuadPart / 1000000.0;
+ }
+
+ StackScratchBuffer scratch;
+ SString threadFrame;
+ threadFrame.Printf("Thread (%d)", commonFields.ThreadID);
+ SString event;
+ event.Printf("{\"Time\" : \"%f\", \"Metric\" : \"1\",\n\"Stack\": [\n\"%s\",\n%s\"%s\"]},", millisecondsSinceTraceStart, message.GetANSI(scratch), strCallStack.GetANSI(scratch), threadFrame.GetANSI(scratch));
+ Write(event);
+}
+
+void EventPipeJsonFile::Write(SString &str)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ StackScratchBuffer scratch;
+ const char * charStr = str.GetANSI(scratch);
+ ULONG inCount = str.GetCount();
+ ULONG outCount;
+ m_pFileStream->Write(charStr, inCount, &outCount);
+
+ if(inCount != outCount)
+ {
+ m_writeErrorEncountered = true;
+ }
+}
+
+void EventPipeJsonFile::FormatCallStack(StackContents &stackContents, SString &resultStr)
+{
+ STANDARD_VM_CONTRACT;
+
+ StackScratchBuffer scratch;
+ SString frameStr;
+
+ for(unsigned int i=0; i<stackContents.GetLength(); i++)
+ {
+ // Get the method declaration string.
+ MethodDesc *pMethod = stackContents.GetMethod(i);
+ _ASSERTE(pMethod != NULL);
+
+ SString mAssemblyName;
+ mAssemblyName.SetUTF8(pMethod->GetLoaderModule()->GetAssembly()->GetSimpleName());
+ SString fullName;
+ TypeString::AppendMethodInternal(
+ fullName,
+ pMethod,
+ TypeString::FormatNamespace | TypeString::FormatSignature);
+
+ frameStr.Printf("\"%s!%s\",\n", mAssemblyName.GetANSI(scratch), fullName.GetANSI(scratch));
+ resultStr.Append(frameStr);
+ }
+}
diff --git a/src/vm/eventpipejsonfile.h b/src/vm/eventpipejsonfile.h
new file mode 100644
index 0000000000..b6e42def68
--- /dev/null
+++ b/src/vm/eventpipejsonfile.h
@@ -0,0 +1,40 @@
+// 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_JSONFILE_H__
+#define __EVENTPIPE_JSONFILE_H__
+
+#include "common.h"
+#include "eventpipe.h"
+#include "fstream.h"
+
+class EventPipeJsonFile
+{
+ public:
+ EventPipeJsonFile(SString &outFilePath);
+ ~EventPipeJsonFile();
+
+ // Write an event with the specified message and stack.
+ void WriteEvent(CommonEventFields &commonFields, SString &message, StackContents &stackContents);
+
+ private:
+
+ // Write a string to the file.
+ void Write(SString &str);
+
+ // Format the input callstack for printing.
+ void FormatCallStack(StackContents &stackContents, SString &resultStr);
+
+ // The output file stream.
+ CFileStream *m_pFileStream;
+
+ // Keep track of if an error has been encountered while writing.
+ bool m_writeErrorEncountered;
+
+ // File-open timestamp for use when calculating event timestamps.
+ LARGE_INTEGER m_fileOpenTimeStamp;
+};
+
+#endif // __EVENTPIPE_JSONFILE_H__
diff --git a/src/vm/sampleprofiler.cpp b/src/vm/sampleprofiler.cpp
new file mode 100644
index 0000000000..004b3c68b0
--- /dev/null
+++ b/src/vm/sampleprofiler.cpp
@@ -0,0 +1,155 @@
+// 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 "sampleprofiler.h"
+#include "hosting.h"
+#include "threadsuspend.h"
+
+Volatile<BOOL> SampleProfiler::s_profilingEnabled = false;
+Thread* SampleProfiler::s_pSamplingThread = NULL;
+CLREventStatic SampleProfiler::s_threadShutdownEvent;
+#ifdef FEATURE_PAL
+long SampleProfiler::s_samplingRateInNs = 1000000; // 1ms
+#endif
+
+// Synchronization of multiple callers occurs in EventPipe::Enable.
+void SampleProfiler::Enable()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(s_pSamplingThread == NULL);
+ }
+ CONTRACTL_END;
+
+ s_profilingEnabled = true;
+ s_pSamplingThread = SetupUnstartedThread();
+ if(s_pSamplingThread->CreateNewThread(0, ThreadProc, NULL))
+ {
+ // Start the sampling thread.
+ s_pSamplingThread->SetBackground(TRUE);
+ s_pSamplingThread->StartThread();
+ }
+ else
+ {
+ _ASSERT(!"Unable to create sample profiler thread.");
+ }
+}
+
+// Synchronization of multiple callers occurs in EventPipe::Disable.
+void SampleProfiler::Disable()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // Bail early if profiling is not enabled.
+ if(!s_profilingEnabled)
+ {
+ return;
+ }
+
+ // Reset the event before shutdown.
+ s_threadShutdownEvent.Reset();
+
+ // The sampling thread will watch this value and exit
+ // when profiling is disabled.
+ s_profilingEnabled = false;
+
+ // Wait for the sampling thread to clean itself up.
+ s_threadShutdownEvent.Wait(0, FALSE /* bAlertable */);
+}
+
+DWORD WINAPI SampleProfiler::ThreadProc(void *args)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(s_pSamplingThread != NULL);
+ }
+ CONTRACTL_END;
+
+ // Complete thread initialization and start the profiling loop.
+ if(s_pSamplingThread->HasStarted())
+ {
+ // Switch to pre-emptive mode so that this thread doesn't starve the GC.
+ GCX_PREEMP();
+
+ while(s_profilingEnabled)
+ {
+ // Check to see if we can suspend managed execution.
+ if(ThreadSuspend::SysIsSuspendInProgress() || (ThreadSuspend::GetSuspensionThread() != 0))
+ {
+ // Skip the current sample.
+#ifdef FEATURE_PAL
+ PAL_nanosleep(s_samplingRateInNs);
+#else
+ ClrSleepEx(1, FALSE);
+#endif
+ continue;
+ }
+
+ // Actually suspend managed execution.
+ ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_REASON::SUSPEND_OTHER);
+
+ // Walk all managed threads and capture stacks.
+ WalkManagedThreads();
+
+ // Resume managed execution.
+ ThreadSuspend::RestartEE(FALSE /* bFinishedGC */, TRUE /* SuspendSucceeded */);
+
+ // Wait until it's time to sample again.
+#ifdef FEATURE_PAL
+ PAL_nanosleep(s_samplingRateInNs);
+#else
+ ClrSleepEx(1, FALSE);
+#endif
+ }
+ }
+
+ // Destroy the sampling thread when done running.
+ DestroyThread(s_pSamplingThread);
+ s_pSamplingThread = NULL;
+
+ // Signal Disable() that the thread has been destroyed.
+ s_threadShutdownEvent.Set();
+
+ return S_OK;
+}
+
+// The thread store lock must already be held by the thread before this function
+// is called. ThreadSuspend::SuspendEE acquires the thread store lock.
+void SampleProfiler::WalkManagedThreads()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ Thread *pThread = NULL;
+ StackContents stackContents;
+
+ // Iterate over all managed threads.
+ // Assumes that the ThreadStoreLock is held because we've suspended all threads.
+ while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL)
+ {
+ // Walk the stack and write it out as an event.
+ if(EventPipe::WalkManagedStackForThread(pThread, stackContents) && !stackContents.IsEmpty())
+ {
+ EventPipe::WriteSampleProfileEvent(pThread, stackContents);
+ }
+ }
+}
diff --git a/src/vm/sampleprofiler.h b/src/vm/sampleprofiler.h
new file mode 100644
index 0000000000..2c7466f4c9
--- /dev/null
+++ b/src/vm/sampleprofiler.h
@@ -0,0 +1,44 @@
+// 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 __SAMPLEPROFILER_H__
+#define __SAMPLEPROFILER_H__
+
+#include "common.h"
+#include "eventpipe.h"
+
+class SampleProfiler
+{
+ public:
+
+ // Enable profiling.
+ static void Enable();
+
+ // Disable profiling.
+ static void Disable();
+
+ private:
+
+ // Iterate through all managed threads and walk all stacks.
+ static void WalkManagedThreads();
+
+ // Profiling thread proc. Invoked on a new thread when profiling is enabled.
+ static DWORD WINAPI SampleProfiler::ThreadProc(void *args);
+
+ // True when profiling is enabled.
+ static Volatile<BOOL> s_profilingEnabled;
+
+ // The sampling thread.
+ static Thread *s_pSamplingThread;
+
+ // Thread shutdown event for synchronization between Disable() and the sampling thread.
+ static CLREventStatic s_threadShutdownEvent;
+
+#ifdef FEATURE_PAL
+ // The sampling rate.
+ static long s_samplingRateInNs;
+#endif
+};
+
+#endif // __SAMPLEPROFILER_H__