From: Brian Robbins Date: Fri, 24 Mar 2017 16:38:28 +0000 (-0700) Subject: Basic EventPipe functionality that supports writing sample profile events to JSON... X-Git-Tag: accepted/tizen/base/20180629.140029~1564^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a387696646a1aa7413c2082a430845844e496313;p=platform%2Fupstream%2Fcoreclr.git Basic EventPipe functionality that supports writing sample profile events to JSON. SampleProfiler that globally suspends the runtime every 1ms to walk stacks and write them to the EventPipe. --- diff --git a/clrdefinitions.cmake b/clrdefinitions.cmake index 9ca67de..c4bfb8e 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 bb6e710..227f986 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 0a285a1..a0c2456 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 8c702fa..b4f6f49 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 1d74358..c51dc44 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 918f92a..d16fb58 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 861d68c..26fcacf 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 602ccc9..1caed49 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" @@ -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 index 0000000..98d382e --- /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 0000000..2978412 --- /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 0000000..6353c91 --- /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; iGetLoaderModule()->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 0000000..b6e42de --- /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 0000000..004b3c6 --- /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 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 0000000..2c7466f --- /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 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__