Basic EventPipe functionality that supports writing sample profile events to JSON...
authorBrian Robbins <brianrob@microsoft.com>
Fri, 24 Mar 2017 16:38:28 +0000 (09:38 -0700)
committerBrian Robbins <brianrob@microsoft.com>
Mon, 3 Apr 2017 20:03:04 +0000 (13:03 -0700)
14 files changed:
clrdefinitions.cmake
src/inc/CrstTypes.def
src/inc/clrconfigvalues.h
src/inc/crsttypes.h
src/pal/inc/pal.h
src/pal/src/misc/time.cpp
src/vm/CMakeLists.txt
src/vm/ceemain.cpp
src/vm/eventpipe.cpp [new file with mode: 0644]
src/vm/eventpipe.h [new file with mode: 0644]
src/vm/eventpipejsonfile.cpp [new file with mode: 0644]
src/vm/eventpipejsonfile.h [new file with mode: 0644]
src/vm/sampleprofiler.cpp [new file with mode: 0644]
src/vm/sampleprofiler.h [new file with mode: 0644]

index 9ca67de..c4bfb8e 100644 (file)
@@ -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)
index bb6e710..227f986 100644 (file)
@@ -778,4 +778,8 @@ End
 
 Crst InlineTrackingMap
     AcquiredBefore IbcProfile
-End
\ No newline at end of file
+End
+
+Crst EventPipe
+    AcquiredBefore ThreadIdDispenser ThreadStore
+End
index 0a285a1..a0c2456 100644 (file)
@@ -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
 // 
 //---------------------------------------------------------------------------------------
index 8c702fa..b4f6f49 100644 (file)
@@ -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)
+
index 1d74358..c51dc44 100644 (file)
@@ -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)(
index 918f92a..d16fb58 100644 (file)
@@ -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;
+}
index 861d68c..26fcacf 100644 (file)
@@ -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
index 602ccc9..1caed49 100644 (file)
 #include "perfmap.h"
 #endif
 
+#include "eventpipe.h"
+
 #ifndef FEATURE_PAL
 // Included for referencing __security_cookie
 #include "process.h"
@@ -1031,7 +1033,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
 
@@ -1700,6 +1707,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 (file)
index 0000000..98d382e
--- /dev/null
@@ -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 (file)
index 0000000..2978412
--- /dev/null
@@ -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 (file)
index 0000000..6353c91
--- /dev/null
@@ -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 (file)
index 0000000..b6e42de
--- /dev/null
@@ -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 (file)
index 0000000..004b3c6
--- /dev/null
@@ -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 (file)
index 0000000..2c7466f
--- /dev/null
@@ -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__