diff options
author | Sean Gillespie <segilles@microsoft.com> | 2018-01-23 18:53:30 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-23 18:53:30 -0800 |
commit | facdc8b97f73973fb416ed13e4b9dd9a255864bf (patch) | |
tree | c6be1ef07de8d324dcb121da524c13458d7f0f63 | |
parent | 0bafdbc71e5a3efe6b6df0cbcf5aee5081a3e4c8 (diff) | |
download | coreclr-facdc8b97f73973fb416ed13e4b9dd9a255864bf.tar.gz coreclr-facdc8b97f73973fb416ed13e4b9dd9a255864bf.tar.bz2 coreclr-facdc8b97f73973fb416ed13e4b9dd9a255864bf.zip |
[Local GC] FEATURE_EVENT_TRACE 1/n: Tracking Event State (#15873)
* [Local GC] FEATURE_EVENT_TRACE 1/n: Add infrastructure for keeping event state within the GC and plumbing to communicate event state changes
* Code review feedback: use a load without a barrier in IsEnabled and put debug-only code under TRACE_GC_EVENT_STATE
* Address code review feedback: add EventPipe callback and comments
* Fix the non-FEATURE_PAL build
* Fix an issue where the GC fails to react to ETW callbacks to occur before the GC is initialized (e.g. on startup when an ETW session is already active)
* Simplify callback locking scheme
* Add a separate callback for each EventPipe provider and funnel them all through a common handler
* Fix non-FEATURE_PAL build
-rw-r--r-- | src/gc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/gc/env/gcenv.h | 4 | ||||
-rw-r--r-- | src/gc/env/gcenv.object.h | 5 | ||||
-rw-r--r-- | src/gc/env/gcenv.sync.h | 4 | ||||
-rw-r--r-- | src/gc/gcee.cpp | 9 | ||||
-rw-r--r-- | src/gc/gceesvr.cpp | 1 | ||||
-rw-r--r-- | src/gc/gceewks.cpp | 1 | ||||
-rw-r--r-- | src/gc/gceventstatus.cpp | 9 | ||||
-rw-r--r-- | src/gc/gceventstatus.h | 190 | ||||
-rw-r--r-- | src/gc/gcimpl.h | 4 | ||||
-rw-r--r-- | src/gc/gcinterface.h | 50 | ||||
-rw-r--r-- | src/gc/sample/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/gc/sample/gcenv.h | 4 | ||||
-rw-r--r-- | src/inc/eventtracebase.h | 36 | ||||
-rw-r--r-- | src/scripts/genEventPipe.py | 5 | ||||
-rw-r--r-- | src/vm/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/vm/eventtrace.cpp | 135 | ||||
-rw-r--r-- | src/vm/gcheaputilities.cpp | 98 | ||||
-rw-r--r-- | src/vm/gcheaputilities.h | 4 |
19 files changed, 542 insertions, 22 deletions
diff --git a/src/gc/CMakeLists.txt b/src/gc/CMakeLists.txt index 3240074b9b..e7aacdb831 100644 --- a/src/gc/CMakeLists.txt +++ b/src/gc/CMakeLists.txt @@ -18,6 +18,7 @@ remove_definitions(-DWRITE_BARRIER_CHECK) add_definitions(-DFEATURE_REDHAWK) set( GC_SOURCES + gceventstatus.cpp gcconfig.cpp gccommon.cpp gcscan.cpp diff --git a/src/gc/env/gcenv.h b/src/gc/env/gcenv.h index 3de756021d..a3071a1397 100644 --- a/src/gc/env/gcenv.h +++ b/src/gc/env/gcenv.h @@ -1,6 +1,8 @@ // 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 __GCENV_H__ +#define __GCENV_H__ #if defined(_DEBUG) #ifndef _DEBUG_IMPL @@ -71,3 +73,5 @@ #include "etmdummy.h" #define ETW_EVENT_ENABLED(e,f) false + +#endif // __GCENV_H__ diff --git a/src/gc/env/gcenv.object.h b/src/gc/env/gcenv.object.h index 4d611e562d..dd152f2f3c 100644 --- a/src/gc/env/gcenv.object.h +++ b/src/gc/env/gcenv.object.h @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#ifndef __GCENV_OBJECT_H__ +#define __GCENV_OBJECT_H__ + //------------------------------------------------------------------------------------------------- // // Low-level types describing GC object layouts. @@ -168,3 +171,5 @@ public: return offsetof(ArrayBase, m_dwLength); } }; + +#endif // __GCENV_OBJECT_H__ diff --git a/src/gc/env/gcenv.sync.h b/src/gc/env/gcenv.sync.h index d6bee05a19..5b7b77ddd4 100644 --- a/src/gc/env/gcenv.sync.h +++ b/src/gc/env/gcenv.sync.h @@ -1,6 +1,8 @@ // 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 __GCENV_SYNC_H__ +#define __GCENV_SYNC_H__ // ----------------------------------------------------------------------------------------------------------- // @@ -143,3 +145,5 @@ private: HANDLE m_hEvent; bool m_fInitialized; }; + +#endif // __GCENV_SYNC_H__ diff --git a/src/gc/gcee.cpp b/src/gc/gcee.cpp index 90939b3d97..c5cc88b373 100644 --- a/src/gc/gcee.cpp +++ b/src/gc/gcee.cpp @@ -687,6 +687,15 @@ bool GCHeap::RuntimeStructuresValid() return GCScan::GetGcRuntimeStructuresValid(); } +void GCHeap::ControlEvents(GCEventKeyword keyword, GCEventLevel level) +{ + GCEventStatus::Set(GCEventProvider_Default, keyword, level); +} + +void GCHeap::ControlPrivateEvents(GCEventKeyword keyword, GCEventLevel level) +{ + GCEventStatus::Set(GCEventProvider_Private, keyword, level); +} #endif // !DACCESS_COMPILE diff --git a/src/gc/gceesvr.cpp b/src/gc/gceesvr.cpp index e216834f8e..cfcbe5869a 100644 --- a/src/gc/gceesvr.cpp +++ b/src/gc/gceesvr.cpp @@ -13,6 +13,7 @@ #include "gc.h" #include "gcscan.h" #include "gchandletableimpl.h" +#include "gceventstatus.h" #define SERVER_GC 1 diff --git a/src/gc/gceewks.cpp b/src/gc/gceewks.cpp index f23038f012..9a4038cdd9 100644 --- a/src/gc/gceewks.cpp +++ b/src/gc/gceewks.cpp @@ -11,6 +11,7 @@ #include "gc.h" #include "gcscan.h" #include "gchandletableimpl.h" +#include "gceventstatus.h" #ifdef SERVER_GC #undef SERVER_GC diff --git a/src/gc/gceventstatus.cpp b/src/gc/gceventstatus.cpp new file mode 100644 index 0000000000..9c4f35bfde --- /dev/null +++ b/src/gc/gceventstatus.cpp @@ -0,0 +1,9 @@ +// 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 "gceventstatus.h" + +Volatile<GCEventLevel> GCEventStatus::enabledLevels[2] = {GCEventLevel_None, GCEventLevel_None}; +Volatile<GCEventKeyword> GCEventStatus::enabledKeywords[2] = {GCEventKeyword_None, GCEventKeyword_None}; diff --git a/src/gc/gceventstatus.h b/src/gc/gceventstatus.h new file mode 100644 index 0000000000..4b7310b8a7 --- /dev/null +++ b/src/gc/gceventstatus.h @@ -0,0 +1,190 @@ +// 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 __GCEVENTSTATUS_H__ +#define __GCEVENTSTATUS_H__ + + +/* + * gceventstatus.h - Eventing status for a standalone GC + * + * In order for a local GC to determine what events are enabled + * in an efficient manner, the GC maintains some local state about + * keywords and levels that are enabled for each eventing provider. + * + * The GC fires events from two providers: the "main" provider + * and the "private" provider. This file tracks keyword and level + * information for each provider separately. + * + * It is the responsibility of the EE to inform the GC of changes + * to eventing state. This is accomplished by invoking the + * `IGCHeap::ControlEvents` and `IGCHeap::ControlPrivateEvents` callbacks + * on the EE's heap instance, which ultimately will enable and disable keywords + * and levels within this file. + */ + +#include "common.h" +#include "gcenv.h" +#include "gc.h" + +// Uncomment this define to print out event state changes to standard error. +// #define TRACE_GC_EVENT_STATE 1 + +/* + * GCEventProvider represents one of the two providers that the GC can + * fire events from: the default and private providers. + */ +enum GCEventProvider +{ + GCEventProvider_Default = 0, + GCEventProvider_Private = 1 +}; + +/* + * GCEventStatus maintains all eventing state for the GC. It consists + * of a keyword bitmask and level for each provider that the GC can use + * to fire events. + * + * A level and event pair are considered to be "enabled" on a given provider + * if the given level is less than or equal to the current enabled level + * and if the keyword is present in the enabled keyword bitmask for that + * provider. + */ +class GCEventStatus +{ +private: + /* + * The enabled level for each provider. + */ + static Volatile<GCEventLevel> enabledLevels[2]; + + /* + * The bitmap of enabled keywords for each provider. + */ + static Volatile<GCEventKeyword> enabledKeywords[2]; + +public: + /* + * IsEnabled queries whether or not the given level and keyword are + * enabled on the given provider, returning true if they are. + */ + __forceinline static bool IsEnabled(GCEventProvider provider, GCEventKeyword keyword, GCEventLevel level) + { + assert(level >= GCEventLevel_None && level < GCEventLevel_Max); + + size_t index = static_cast<size_t>(provider); + return (enabledLevels[index].LoadWithoutBarrier() >= level) + && (enabledKeywords[index].LoadWithoutBarrier() & keyword); + } + + /* + * Set sets the eventing state (level and keyword bitmap) for a given + * provider to the provided values. + */ + static void Set(GCEventProvider provider, GCEventKeyword keywords, GCEventLevel level) + { + assert(level >= GCEventLevel_None && level < GCEventLevel_Max); + + size_t index = static_cast<size_t>(provider); + + enabledLevels[index] = level; + enabledKeywords[index] = keywords; + +#if TRACE_GC_EVENT_STATE + fprintf(stderr, "event state change:\n"); + DebugDumpState(provider); +#endif // TRACE_GC_EVENT_STATE + } + +#if TRACE_GC_EVENT_STATE +private: + static void DebugDumpState(GCEventProvider provider) + { + size_t index = static_cast<size_t>(provider); + GCEventLevel level = enabledLevels[index]; + GCEventKeyword keyword = enabledKeywords[index]; + if (provider == GCEventProvider_Default) + { + fprintf(stderr, "provider: default\n"); + } + else + { + fprintf(stderr, "provider: private\n"); + } + + switch (level) + { + case GCEventLevel_None: + fprintf(stderr, " level: None\n"); + break; + case GCEventLevel_Fatal: + fprintf(stderr, " level: Fatal\n"); + break; + case GCEventLevel_Error: + fprintf(stderr, " level: Error\n"); + break; + case GCEventLevel_Warning: + fprintf(stderr, " level: Warning\n"); + break; + case GCEventLevel_Information: + fprintf(stderr, " level: Information\n"); + break; + case GCEventLevel_Verbose: + fprintf(stderr, " level: Verbose\n"); + break; + default: + fprintf(stderr, " level: %d?\n", level); + break; + } + + fprintf(stderr, " keywords: "); + if (keyword & GCEventKeyword_GC) + { + fprintf(stderr, "GC "); + } + + if (keyword & GCEventKeyword_GCHandle) + { + fprintf(stderr, "GCHandle "); + } + + if (keyword & GCEventKeyword_GCHeapDump) + { + fprintf(stderr, "GCHeapDump "); + } + + if (keyword & GCEventKeyword_GCSampledObjectAllocationHigh) + { + fprintf(stderr, "GCSampledObjectAllocationHigh "); + } + + if (keyword & GCEventKeyword_GCHeapSurvivalAndMovement) + { + fprintf(stderr, "GCHeapSurvivalAndMovement "); + } + + if (keyword & GCEventKeyword_GCHeapCollect) + { + fprintf(stderr, "GCHeapCollect "); + } + + if (keyword & GCEventKeyword_GCHeapAndTypeNames) + { + fprintf(stderr, "GCHeapAndTypeNames "); + } + + if (keyword & GCEventKeyword_GCSampledObjectAllocationLow) + { + fprintf(stderr, "GCSampledObjectAllocationLow "); + } + + fprintf(stderr, "\n"); + } +#endif // TRACE_GC_EVENT_STATUS + + // This class is a singleton and can't be instantiated. + GCEventStatus() = delete; +}; + +#endif // __GCEVENTSTATUS_H__ diff --git a/src/gc/gcimpl.h b/src/gc/gcimpl.h index c0efa69531..7210b9bcf0 100644 --- a/src/gc/gcimpl.h +++ b/src/gc/gcimpl.h @@ -230,6 +230,10 @@ public: // FIX virtual segment_handle RegisterFrozenSegment(segment_info *pseginfo); virtual void UnregisterFrozenSegment(segment_handle seg); + // Event control functions + void ControlEvents(GCEventKeyword keyword, GCEventLevel level); + void ControlPrivateEvents(GCEventKeyword keyword, GCEventLevel level); + void WaitUntilConcurrentGCComplete (); // Use in managd threads #ifndef DACCESS_COMPILE HRESULT WaitUntilConcurrentGCCompleteAsync(int millisecondsTimeout); // Use in native threads. TRUE if succeed. FALSE if failed or timeout diff --git a/src/gc/gcinterface.h b/src/gc/gcinterface.h index afd3db3fe4..26e7c096d2 100644 --- a/src/gc/gcinterface.h +++ b/src/gc/gcinterface.h @@ -205,6 +205,44 @@ extern uint8_t* g_shadow_lowest_address; // For low memory notification from host extern int32_t g_bLowMemoryFromHost; +// Event levels corresponding to events that can be fired by the GC. +enum GCEventLevel +{ + GCEventLevel_None = 0, + GCEventLevel_Fatal = 1, + GCEventLevel_Error = 2, + GCEventLevel_Warning = 3, + GCEventLevel_Information = 4, + GCEventLevel_Verbose = 5, + GCEventLevel_Max = 6 +}; + +// Event keywords corresponding to events that can be fired by the GC. These +// numbers come from the ETW manifest itself - please make changes to this enum +// if you add, remove, or change keyword sets that are used by the GC! +enum GCEventKeyword +{ + GCEventKeyword_None = 0x0, + GCEventKeyword_GC = 0x1, + GCEventKeyword_GCHandle = 0x2, + GCEventKeyword_GCHeapDump = 0x100000, + GCEventKeyword_GCSampledObjectAllocationHigh = 0x200000, + GCEventKeyword_GCHeapSurvivalAndMovement = 0x400000, + GCEventKeyword_GCHeapCollect = 0x800000, + GCEventKeyword_GCHeapAndTypeNames = 0x1000000, + GCEventKeyword_GCSampledObjectAllocationLow = 0x2000000, + GCEventKeyword_All = GCEventKeyword_GC + | GCEventKeyword_GCHandle + | GCEventKeyword_GCHeapDump + | GCEventKeyword_GCSampledObjectAllocationHigh + | GCEventKeyword_GCHeapDump + | GCEventKeyword_GCSampledObjectAllocationHigh + | GCEventKeyword_GCHeapSurvivalAndMovement + | GCEventKeyword_GCHeapCollect + | GCEventKeyword_GCHeapAndTypeNames + | GCEventKeyword_GCSampledObjectAllocationLow +}; + // !!!!!!!!!!!!!!!!!!!!!!! // make sure you change the def in bcl\system\gc.cs // if you change this! @@ -809,6 +847,18 @@ public: // Unregisters a frozen segment. virtual void UnregisterFrozenSegment(segment_handle seg) = 0; + /* + =========================================================================== + Routines for informing the GC about which events are enabled. + =========================================================================== + */ + + // Enables or disables the given keyword or level on the default event provider. + virtual void ControlEvents(GCEventKeyword keyword, GCEventLevel level) = 0; + + // Enables or disables the given keyword or level on the private event provider. + virtual void ControlPrivateEvents(GCEventKeyword keyword, GCEventLevel level) = 0; + IGCHeap() {} virtual ~IGCHeap() {} }; diff --git a/src/gc/sample/CMakeLists.txt b/src/gc/sample/CMakeLists.txt index 6f8aa615d7..953d0498e0 100644 --- a/src/gc/sample/CMakeLists.txt +++ b/src/gc/sample/CMakeLists.txt @@ -10,6 +10,7 @@ add_definitions(-DFEATURE_REDHAWK) set(SOURCES GCSample.cpp gcenv.ee.cpp + ../gceventstatus.cpp ../gcconfig.cpp ../gccommon.cpp ../gceewks.cpp diff --git a/src/gc/sample/gcenv.h b/src/gc/sample/gcenv.h index a86cf535d5..54b596e141 100644 --- a/src/gc/sample/gcenv.h +++ b/src/gc/sample/gcenv.h @@ -1,6 +1,8 @@ // 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 __GCENV_H__ +#define __GCENV_H__ // The sample is to be kept simple, so building the sample // in tandem with a standalone GC is currently not supported. @@ -200,3 +202,5 @@ extern EEConfig * g_pConfig; #include "etmdummy.h" #define ETW_EVENT_ENABLED(e,f) false + +#endif // __GCENV_H__ diff --git a/src/inc/eventtracebase.h b/src/inc/eventtracebase.h index d19e3e97dc..344296f016 100644 --- a/src/inc/eventtracebase.h +++ b/src/inc/eventtracebase.h @@ -251,6 +251,42 @@ public: #if defined(FEATURE_EVENT_TRACE) +VOID EventPipeEtwCallbackDotNETRuntimeStress( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PVOID FilterData, + _Inout_opt_ PVOID CallbackContext); + +VOID EventPipeEtwCallbackDotNETRuntime( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PVOID FilterData, + _Inout_opt_ PVOID CallbackContext); + +VOID EventPipeEtwCallbackDotNETRuntimeRundown( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PVOID FilterData, + _Inout_opt_ PVOID CallbackContext); + +VOID EventPipeEtwCallbackDotNETRuntimePrivate( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PVOID FilterData, + _Inout_opt_ PVOID CallbackContext); + #ifndef FEATURE_PAL // Callback and stack support #if !defined(DONOT_DEFINE_ETW_CALLBACK) && !defined(DACCESS_COMPILE) diff --git a/src/scripts/genEventPipe.py b/src/scripts/genEventPipe.py index 96755ea459..fc4570bf14 100644 --- a/src/scripts/genEventPipe.py +++ b/src/scripts/genEventPipe.py @@ -130,6 +130,7 @@ def generateClrEventPipeWriteEventsImpl( WriteEventImpl.append("\n return ERROR_SUCCESS;\n}\n\n") # EventPipeProvider and EventPipeEvent initialization + callbackName = 'EventPipeEtwCallback' + providerPrettyName if extern: WriteEventImpl.append('extern "C" ') WriteEventImpl.append( "void Init" + @@ -140,7 +141,7 @@ def generateClrEventPipeWriteEventsImpl( providerPrettyName + " = EventPipe::CreateProvider(SL(" + providerPrettyName + - "Name));\n") + "Name), " + callbackName + ");\n") for eventNode in eventNodes: eventName = eventNode.getAttribute('symbol') templateName = eventNode.getAttribute('template') @@ -522,4 +523,4 @@ def main(argv): if __name__ == '__main__': return_code = main(sys.argv[1:]) - sys.exit(return_code)
\ No newline at end of file + sys.exit(return_code) diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 97ab656f81..4e2b8d6838 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -259,6 +259,7 @@ set(VM_SOURCES_WKS set(GC_SOURCES_WKS ${GC_SOURCES_DAC_AND_WKS_COMMON} + ../gc/gceventstatus.cpp ../gc/gcconfig.cpp ../gc/gccommon.cpp ../gc/gcscan.cpp @@ -521,4 +522,4 @@ add_subdirectory(wks) if(FEATURE_PERFTRACING) add_subdirectory(${GENERATED_EVENTING_DIR}/eventpipe ${CMAKE_CURRENT_BINARY_DIR}/eventpipe) -endif(FEATURE_PERFTRACING)
\ No newline at end of file +endif(FEATURE_PERFTRACING) diff --git a/src/vm/eventtrace.cpp b/src/vm/eventtrace.cpp index f66c92e40e..533a3bc7fa 100644 --- a/src/vm/eventtrace.cpp +++ b/src/vm/eventtrace.cpp @@ -4313,6 +4313,110 @@ void InitializeEventTracing() ETW::TypeSystemLog::PostRegistrationInit(); } +// Plumbing to funnel event pipe callbacks and ETW callbacks together into a single common +// handler, for the purposes of informing the GC of changes to the event state. +// +// There is one callback for every EventPipe provider and one for all of ETW. The reason +// for this is that ETW passes the registration handle of the provider that was enabled +// as a field on the "CallbackContext" field of the callback, while EventPipe passes null +// unless another token is given to it when the provider is constructed. In the absence of +// a suitable token, this implementation has a different callback for every EventPipe provider +// that ultimately funnels them all into a common handler. + +// CallbackProviderIndex provides a quick identification of which provider triggered the +// ETW callback. +enum CallbackProviderIndex +{ + DotNETRuntime = 0, + DotNETRuntimeRundown = 1, + DotNETRuntimeStress = 2, + DotNETRuntimePrivate = 3 +}; + +// Common handler for all ETW or EventPipe event notifications. Based on the provider that +// was enabled/disabled, this implementation forwards the event state change onto GCHeapUtilities +// which will inform the GC to update its local state about what events are enabled. +VOID EtwCallbackCommon( + CallbackProviderIndex ProviderIndex, + ULONG ControlCode, + UCHAR Level, + ULONGLONG MatchAnyKeyword) +{ + LIMITED_METHOD_CONTRACT; + + bool bIsPublicTraceHandle = ProviderIndex == DotNETRuntime; +#if !defined(FEATURE_PAL) + static_assert(GCEventLevel_None == TRACE_LEVEL_NONE, "GCEventLevel_None value mismatch"); + static_assert(GCEventLevel_Fatal == TRACE_LEVEL_FATAL, "GCEventLevel_Fatal value mismatch"); + static_assert(GCEventLevel_Error == TRACE_LEVEL_ERROR, "GCEventLevel_Error value mismatch"); + static_assert(GCEventLevel_Warning == TRACE_LEVEL_WARNING, "GCEventLevel_Warning mismatch"); + static_assert(GCEventLevel_Information == TRACE_LEVEL_INFORMATION, "GCEventLevel_Information mismatch"); + static_assert(GCEventLevel_Verbose == TRACE_LEVEL_VERBOSE, "GCEventLevel_Verbose mismatch"); +#endif // !defined(FEATURE_PAL) + GCEventKeyword keywords = static_cast<GCEventKeyword>(MatchAnyKeyword); + GCEventLevel level = static_cast<GCEventLevel>(Level); + GCHeapUtilities::RecordEventStateChange(bIsPublicTraceHandle, keywords, level); +} + +// Individual callbacks for each EventPipe provider. + +VOID EventPipeEtwCallbackDotNETRuntimeStress( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PVOID FilterData, + _Inout_opt_ PVOID CallbackContext) +{ + LIMITED_METHOD_CONTRACT; + + EtwCallbackCommon(DotNETRuntimeStress, ControlCode, Level, MatchAnyKeyword); +} + +VOID EventPipeEtwCallbackDotNETRuntime( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PVOID FilterData, + _Inout_opt_ PVOID CallbackContext) +{ + LIMITED_METHOD_CONTRACT; + + EtwCallbackCommon(DotNETRuntime, ControlCode, Level, MatchAnyKeyword); +} + +VOID EventPipeEtwCallbackDotNETRuntimeRundown( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PVOID FilterData, + _Inout_opt_ PVOID CallbackContext) +{ + LIMITED_METHOD_CONTRACT; + + EtwCallbackCommon(DotNETRuntimeRundown, ControlCode, Level, MatchAnyKeyword); +} + +VOID EventPipeEtwCallbackDotNETRuntimePrivate( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PVOID FilterData, + _Inout_opt_ PVOID CallbackContext) +{ + WRAPPER_NO_CONTRACT; + + EtwCallbackCommon(DotNETRuntimePrivate, ControlCode, Level, MatchAnyKeyword); +} + + #if !defined(FEATURE_PAL) HRESULT ETW::CEtwTracer::Register() { @@ -4397,7 +4501,6 @@ extern "C" extern "C" { - // #EtwCallback: // During the build, MC generates the code to register our provider, and to register // our ETW callback. (This is buried under Intermediates, in a path like @@ -4446,12 +4549,30 @@ extern "C" BOOLEAN bIsRundownTraceHandle = (context->RegistrationHandle==Microsoft_Windows_DotNETRuntimeRundownHandle); - // TypeSystemLog needs a notification when certain keywords are modified, so - // give it a hook here. - if (g_fEEStarted && !g_fEEShutDown && bIsPublicTraceHandle) - { - ETW::TypeSystemLog::OnKeywordsChanged(); - } + // EventPipeEtwCallback contains some GC eventing functionality shared between EventPipe and ETW. + // Eventually, we'll want to merge these two codepaths whenever we can. + CallbackProviderIndex providerIndex = DotNETRuntime; + if (context->RegistrationHandle == Microsoft_Windows_DotNETRuntimeHandle) { + providerIndex = DotNETRuntime; + } else if (context->RegistrationHandle == Microsoft_Windows_DotNETRuntimeRundownHandle) { + providerIndex = DotNETRuntimeRundown; + } else if (context->RegistrationHandle == Microsoft_Windows_DotNETRuntimeStressHandle) { + providerIndex = DotNETRuntimeStress; + } else if (context->RegistrationHandle == Microsoft_Windows_DotNETRuntimePrivateHandle) { + providerIndex = DotNETRuntimePrivate; + } else { + assert(!"unknown registration handle"); + return; + } + + EtwCallbackCommon(providerIndex, ControlCode, Level, MatchAnyKeyword); + + // TypeSystemLog needs a notification when certain keywords are modified, so + // give it a hook here. + if (g_fEEStarted && !g_fEEShutDown && bIsPublicTraceHandle) + { + ETW::TypeSystemLog::OnKeywordsChanged(); + } // A manifest based provider can be enabled to multiple event tracing sessions // As long as there is atleast 1 enabled session, IsEnabled will be TRUE diff --git a/src/vm/gcheaputilities.cpp b/src/vm/gcheaputilities.cpp index af2971f1ab..3ffb3de479 100644 --- a/src/vm/gcheaputilities.cpp +++ b/src/vm/gcheaputilities.cpp @@ -80,6 +80,78 @@ HMODULE GCHeapUtilities::GetGCModule() namespace { +// This block of code contains all of the state necessary to handle incoming +// EtwCallbacks before the GC has been initialized. This is a tricky problem +// because EtwCallbacks can appear at any time, even when we are just about +// finished initializing the GC. +// +// The below lock is taken by the "main" thread (the thread in EEStartup) and +// the "ETW" thread, the one calling EtwCallback. EtwCallback may or may not +// be called on the main thread. +DangerousNonHostedSpinLock g_eventStashLock; + +GCEventLevel g_stashedLevel = GCEventLevel_None; +GCEventKeyword g_stashedKeyword = GCEventKeyword_None; +GCEventLevel g_stashedPrivateLevel = GCEventLevel_None; +GCEventKeyword g_stashedPrivateKeyword = GCEventKeyword_None; + +BOOL g_gcEventTracingInitialized = FALSE; + +// FinalizeLoad is called by the main thread to complete initialization of the GC. +// At this point, the GC has provided us with an IGCHeap instance and we are preparing +// to "publish" it by assigning it to g_pGCHeap. +// +// This function can proceed concurrently with StashKeywordAndLevel below. +void FinalizeLoad(IGCHeap* gcHeap, IGCHandleManager* handleMgr, HMODULE gcModule) +{ + g_pGCHeap = gcHeap; + + { + DangerousNonHostedSpinLockHolder lockHolder(&g_eventStashLock); + + // Ultimately, g_eventStashLock ensures that no two threads call ControlEvents at any + // point in time. + g_pGCHeap->ControlEvents(g_stashedKeyword, g_stashedLevel); + g_pGCHeap->ControlPrivateEvents(g_stashedPrivateKeyword, g_stashedPrivateLevel); + g_gcEventTracingInitialized = TRUE; + } + + g_pGCHandleManager = handleMgr; + g_gcDacGlobals = &g_gc_dac_vars; + g_gc_load_status = GC_LOAD_STATUS_LOAD_COMPLETE; + g_gc_module = gcModule; + LOG((LF_GC, LL_INFO100, "GC load successful\n")); +} + +void StashKeywordAndLevel(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level) +{ + DangerousNonHostedSpinLockHolder lockHolder(&g_eventStashLock); + if (!g_gcEventTracingInitialized) + { + if (isPublicProvider) + { + g_stashedKeyword = keywords; + g_stashedLevel = level; + } + else + { + g_stashedPrivateKeyword = keywords; + g_stashedPrivateLevel = level; + } + } + else + { + if (isPublicProvider) + { + g_pGCHeap->ControlEvents(keywords, level); + } + else + { + g_pGCHeap->ControlPrivateEvents(keywords, level); + } + } +} + // Loads and initializes a standalone GC, given the path to the GC // that we should load. Returns S_OK on success and the failed HRESULT // on failure. @@ -153,12 +225,7 @@ HRESULT LoadAndInitializeGC(LPWSTR standaloneGcLocation) HRESULT initResult = initFunc(gcToClr, &heap, &manager, &g_gc_dac_vars); if (initResult == S_OK) { - g_pGCHeap = heap; - g_pGCHandleManager = manager; - g_gcDacGlobals = &g_gc_dac_vars; - g_gc_load_status = GC_LOAD_STATUS_LOAD_COMPLETE; - g_gc_module = hMod; - LOG((LF_GC, LL_INFO100, "GC load successful\n")); + FinalizeLoad(heap, manager, hMod); } else { @@ -198,12 +265,7 @@ HRESULT InitializeDefaultGC() HRESULT initResult = GC_Initialize(nullptr, &heap, &manager, &g_gc_dac_vars); if (initResult == S_OK) { - g_pGCHeap = heap; - g_pGCHandleManager = manager; - g_gcDacGlobals = &g_gc_dac_vars; - g_gc_load_status = GC_LOAD_STATUS_LOAD_COMPLETE; - g_gc_module = GetModuleInst(); - LOG((LF_GC, LL_INFO100, "GC load successful\n")); + FinalizeLoad(heap, manager, GetModuleInst()); } else { @@ -244,4 +306,16 @@ HRESULT GCHeapUtilities::LoadAndInitialize() } } +void GCHeapUtilities::RecordEventStateChange(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level) +{ + CONTRACTL { + MODE_ANY; + NOTHROW; + GC_NOTRIGGER; + CAN_TAKE_LOCK; + } CONTRACTL_END; + + StashKeywordAndLevel(isPublicProvider, keywords, level); +} + #endif // DACCESS_COMPILE diff --git a/src/vm/gcheaputilities.h b/src/vm/gcheaputilities.h index 7dae8c23f1..2208d4421d 100644 --- a/src/vm/gcheaputilities.h +++ b/src/vm/gcheaputilities.h @@ -202,6 +202,10 @@ public: // Loads (if using a standalone GC) and initializes the GC. static HRESULT LoadAndInitialize(); + + // Records a change in eventing state. This ultimately will inform the GC that it needs to be aware + // of new events being enabled. + static void RecordEventStateChange(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level); #endif // DACCESS_COMPILE private: |