Add Basic Out-of-Process EventPipe Control and Heap Snapshot Capture (#19720)
authorBrian Robbins <brianrob@microsoft.com>
Fri, 7 Sep 2018 20:48:21 +0000 (13:48 -0700)
committerGitHub <noreply@github.com>
Fri, 7 Sep 2018 20:48:21 +0000 (13:48 -0700)
src/System.Private.CoreLib/System.Private.CoreLib.csproj
src/System.Private.CoreLib/src/System/AppDomain.cs
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipe.cs
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs [new file with mode: 0644]
src/inc/clrconfigvalues.h
src/vm/ceemain.cpp
src/vm/eventpipe.cpp
src/vm/eventpipe.h
src/vm/eventtrace.cpp
src/vm/eventtracepriv.h

index d50c66f..30b5b2b 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\DotNETRuntimeEventSource.cs" />
     <Compile Include="$(IntermediateOutputPath)..\eventing\DotNETRuntimeEventSource.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipe.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeController.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeEventDispatcher.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeEventProvider.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeMetadataGenerator.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\Serialization\FormatterServices.cs" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="$(BclSourcesRoot)\System\Runtime\Versioning\CompatibilitySwitch.cs" />
+  </ItemGroup>
+  <ItemGroup>
     <Compile Include="$(BclSourcesRoot)\Microsoft\Win32\UnsafeNativeMethods.cs" />
     <Compile Include="$(BclSourcesRoot)\Microsoft\Win32\Win32Native.cs" />
     <Compile Condition="'$(FeatureWin32Registry)' == 'true'" Include="$(BclSourcesRoot)\Microsoft\Win32\RegistryKey.cs" />
     <Compile Include="$(BclSourcesRoot)\Interop\Unix\Interop.Libraries.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Globalization\CultureInfo.Unix.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Globalization\GlobalizationMode.Unix.cs" />
-    <Compile Include="$(BclSourcesRoot)\System\Runtime\Versioning\CompatibilitySwitch.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Unix.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsWindows)' == 'true'">
index be1330b..45ddeaa 100644 (file)
@@ -656,6 +656,8 @@ namespace System
                     SetupFusionStore(new AppDomainSetup(), null);
                 }
             }
+
+            System.Diagnostics.Tracing.EventPipeController.Initialize();
         }
 
         [MethodImpl(MethodImplOptions.InternalCall)]
index 94ace3c..d83dff8 100644 (file)
@@ -127,6 +127,19 @@ namespace System.Diagnostics.Tracing
                 loggingLevel));
         }
 
+        private void EnableProviderConfiguration(EventPipeProviderConfiguration providerConfig)
+        {
+            m_providers.Add(providerConfig);
+        }
+
+        internal void EnableProviderRange(EventPipeProviderConfiguration[] providerConfigs)
+        {
+            foreach(EventPipeProviderConfiguration config in providerConfigs)
+            {
+                EnableProviderConfiguration(config);
+            }
+        }
+
         internal void SetProfilerSamplingRate(TimeSpan minTimeBetweenSamples)
         {
             if(minTimeBetweenSamples.Ticks <= 0)
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs
new file mode 100644 (file)
index 0000000..8a9a54b
--- /dev/null
@@ -0,0 +1,300 @@
+// 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.
+#if FEATURE_PERFTRACING
+using Internal.IO;
+using Microsoft.Win32;
+using System.IO;
+using System.Globalization;
+using System.Runtime.Versioning;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Diagnostics.Tracing
+{
+    /// <summary>
+    /// Simple out-of-process listener for controlling EventPipe.
+    /// The following environment variables are used to configure EventPipe:
+    ///  - COMPlus_EnableEventPipe=1 : Enable EventPipe immediately for the life of the process.
+    ///  - COMPlus_EnableEventPipe=4 : Enables this controller and creates a thread to listen for enable/disable events.
+    ///  - COMPlus_EventPipeConfig : Provides the configuration in xperf string form for which providers/keywords/levels to be enabled.
+    ///                              If not specified, the default configuration is used.
+    ///  - COMPlus_EventPipeOutputFile : The full path to the netperf file to be written.
+    ///  - COMPlus_EventPipeCircularMB : The size in megabytes of the circular buffer.
+    /// Once the configuration is set and this controller is enabled, tracing is enabled by creating a marker file that this controller listens for.
+    /// Tracing is disabled by deleting the marker file.  The marker file is the target trace file path with ".ctl" appended to it.  For example,
+    /// if the trace file is /path/to/trace.netperf then the marker file is /path/to/trace.netperf.ctl.
+    /// This listener does not poll very often, and thus takes time to enable and disable tracing.  This is by design to ensure that the listener does
+    /// not starve other threads on the system.
+    /// NOTE: If COMPlus_EnableEventPipe != 4 then this listener is not created and does not add any overhead to the process.
+    /// </summary>
+    internal sealed class EventPipeController
+    {
+        // Miscellaneous constants.
+        private const string NetPerfFileExtension = ".netperf";
+        private const string MarkerFileExtension = ".ctl";
+        private const int EnabledPollingIntervalMilliseconds = 1000; // 1 second
+        private const int DisabledPollingIntervalMilliseconds = 10000; // 10 seconds
+        private const uint DefaultCircularBufferMB = 1024; // 1 GB
+        private static readonly char[] ProviderConfigDelimiter = new char[] { ',' };
+        private static readonly char[] ConfigComponentDelimiter = new char[] { ':' };
+
+        // The default set of providers/keywords/levels.  Used if an alternative configuration is not specified.
+        private static readonly EventPipeProviderConfiguration[] DefaultProviderConfiguration = new EventPipeProviderConfiguration[]
+        {
+            new EventPipeProviderConfiguration("Microsoft-Windows-DotNETRuntime", 0x4c14fccbd, 5),
+            new EventPipeProviderConfiguration("Microsoft-Windows-DotNETRuntimePrivate", 0x4002000b, 5),
+            new EventPipeProviderConfiguration("Microsoft-DotNETCore-SampleProfiler", 0x0, 5)
+        };
+
+        // Cache for COMPlus configuration variables.
+        private static int s_Config_EnableEventPipe = -1;
+        private static string s_Config_EventPipeConfig = null;
+        private static uint s_Config_EventPipeCircularMB = 0;
+        private static string s_Config_EventPipeOutputFile = null;
+
+        // Singleton controller instance.
+        private static EventPipeController s_controllerInstance = null;
+
+        // Controller object state.
+        private Timer m_timer;
+        private string m_traceFilePath = null;
+        private string m_markerFilePath = null;
+        private bool m_markerFileExists = false;
+
+        internal static void Initialize()
+        {
+            // Don't allow failures to propagate upstream.
+            // Instead, ensure program correctness without tracing.
+            try
+            {
+                if (s_controllerInstance == null)
+                {
+                    if(Config_EnableEventPipe == 4)
+                    {
+                        // Create a new controller to listen for commands.
+                        s_controllerInstance = new EventPipeController();
+                    }
+                    else if (Config_EnableEventPipe > 0)
+                    {
+                        // Enable tracing immediately.
+                        // It will be disabled automatically on shutdown.
+                        EventPipe.Enable(GetConfiguration());
+                    }
+                }
+            }
+            catch { }
+        }
+
+        private EventPipeController()
+        {
+            // Initialize the timer to run once.  The timer will re-schedule itself after each poll operation.
+            // This is done to ensure that there aren't multiple concurrent polling operations when an operation
+            // takes longer than the polling interval (e.g. on disable/rundown).
+            m_timer = new Timer(
+                callback: new TimerCallback(PollForTracingCommand),
+                state: null,
+                dueTime: DisabledPollingIntervalMilliseconds,
+                period: Timeout.Infinite,
+                flowExecutionContext: false);
+        }
+
+        private void PollForTracingCommand(object state)
+        {
+            // Make sure that any transient errors don't cause the listener thread to exit.
+            try
+            {
+                // Perform initialization when the timer fires for the first time.
+                if (m_traceFilePath == null)
+                {
+                    // Set file paths.
+                    m_traceFilePath = GetDisambiguatedTraceFilePath(Config_EventPipeOutputFile);
+                    m_markerFilePath = MarkerFilePath;
+
+                    // Marker file is assumed to not exist.
+                    // This will be updated when the monitoring thread starts.
+                    m_markerFileExists = false;
+                }
+
+                // Check for existence of the file.
+                // If the existence of the file has changed since the last time we checked
+                // this means that we need to act on that change.
+                bool fileExists = File.Exists(m_markerFilePath);
+                if (m_markerFileExists != fileExists)
+                {
+                    // Save the result.
+                    m_markerFileExists = fileExists;
+
+                    // Take the appropriate action.
+                    if (fileExists)
+                    {
+                        // Enable tracing.
+                        EventPipe.Enable(GetConfiguration());
+                    }
+                    else
+                    {
+                        // Disable tracing.
+                        EventPipe.Disable();
+                    }
+                }
+
+                // Schedule the timer to run again.
+                m_timer.Change(fileExists ? EnabledPollingIntervalMilliseconds : DisabledPollingIntervalMilliseconds, Timeout.Infinite);
+            }
+            catch { }
+        }
+
+        private static EventPipeConfiguration GetConfiguration()
+        {
+            // Create a new configuration object.
+            EventPipeConfiguration config = new EventPipeConfiguration(
+                GetDisambiguatedTraceFilePath(Config_EventPipeOutputFile),
+                Config_EventPipeCircularMB);
+
+            // Get the configuration.
+            string strConfig = Config_EventPipeConfig;
+            if (!string.IsNullOrEmpty(strConfig))
+            {
+                // String must be of the form "providerName:keywords:level,providerName:keywords:level..."
+                string[] providers = strConfig.Split(ProviderConfigDelimiter);
+                foreach (string provider in providers)
+                {
+                    string[] components = provider.Split(ConfigComponentDelimiter);
+                    if (components.Length == 3)
+                    {
+                        string providerName = components[0];
+
+                        // We use a try/catch block here because ulong.TryParse won't accept 0x at the beginning
+                        // of a hex string.  Thus, we either need to conditionally strip it or handle the exception.
+                        // Given that this is not a perf-critical path, catching the exception is the simpler code.
+                        ulong keywords = 0;
+                        try
+                        {
+                            keywords = Convert.ToUInt64(components[1], 16);
+                        }
+                        catch { }
+
+                        uint level;
+                        if (!uint.TryParse(components[2], out level))
+                        {
+                            level = 0;
+                        }
+
+                        config.EnableProvider(providerName, keywords, level);
+                    }
+                }
+            }
+            else
+            {
+                // Specify the default configuration.
+                config.EnableProviderRange(DefaultProviderConfiguration);
+            }
+
+            return config;
+        }
+
+        /// <summary>
+        /// Responsible for disambiguating the trace file path if the specified file already exists.
+        /// This can happen if there are multiple applications with tracing enabled concurrently and COMPlus_EventPipeOutputFile
+        /// is set to the same value for more than one concurrently running application.
+        /// </summary>
+        private static string GetDisambiguatedTraceFilePath(string inputPath)
+        {
+            if (string.IsNullOrEmpty(inputPath))
+            {
+                throw new ArgumentNullException("inputPath");
+            }
+
+            string filePath = inputPath;
+            if (File.Exists(filePath))
+            {
+                string directoryName = Path.GetDirectoryName(filePath);
+                string fileWithoutExtension = Path.GetFileName(filePath);
+                string extension = Path.GetExtension(filePath);
+
+                string newFileWithExtension = fileWithoutExtension + "." + Win32Native.GetCurrentProcessId() + extension;
+                filePath = Path.Combine(directoryName, newFileWithExtension);
+            }
+
+            return filePath;
+        }
+
+        #region Configuration
+
+        private static int Config_EnableEventPipe
+        {
+            get
+            {
+                if (s_Config_EnableEventPipe == -1)
+                {
+                    string strEnabledValue = CompatibilitySwitch.GetValueInternal("EnableEventPipe");
+                    if ((strEnabledValue == null) || (!int.TryParse(strEnabledValue, out s_Config_EnableEventPipe)))
+                    {
+                        s_Config_EnableEventPipe = 0;
+                    }
+                }
+
+                return s_Config_EnableEventPipe;
+            }
+        }
+
+        private static string Config_EventPipeConfig
+        {
+            get
+            {
+                if (s_Config_EventPipeConfig == null)
+                {
+                    s_Config_EventPipeConfig = CompatibilitySwitch.GetValueInternal("EventPipeConfig");
+                }
+
+                return s_Config_EventPipeConfig;
+            }
+        }
+
+        private static uint Config_EventPipeCircularMB
+        {
+            get
+            {
+                if (s_Config_EventPipeCircularMB == 0)
+                {
+                    string strCircularMB = CompatibilitySwitch.GetValueInternal("EventPipeCircularMB");
+                    if ((strCircularMB == null) || (!uint.TryParse(strCircularMB, out s_Config_EventPipeCircularMB)))
+                    {
+                        s_Config_EventPipeCircularMB = DefaultCircularBufferMB;
+                    }
+                }
+
+                return s_Config_EventPipeCircularMB;
+            }
+        }
+
+        private static string Config_EventPipeOutputFile
+        {
+            get
+            {
+                if (s_Config_EventPipeOutputFile == null)
+                {
+                    s_Config_EventPipeOutputFile = CompatibilitySwitch.GetValueInternal("EventPipeOutputFile");
+                    if (s_Config_EventPipeOutputFile == null)
+                    {
+                        s_Config_EventPipeOutputFile = "Process-" + Win32Native.GetCurrentProcessId() + NetPerfFileExtension;
+                    }
+                }
+
+                return s_Config_EventPipeOutputFile;
+            }
+        }
+
+        private static string MarkerFilePath
+        {
+            get
+            {
+                return Config_EventPipeOutputFile + MarkerFileExtension;
+            }
+        }
+
+        #endregion Configuration
+    }
+}
+
+#endif // FEATURE_PERFTRACING
index 0d843fb..a3b6515 100644 (file)
@@ -741,6 +741,7 @@ RETAIL_CONFIG_DWORD_INFO(INTERNAL_EnableEventPipe, W("EnableEventPipe"), 0, "Ena
 RETAIL_CONFIG_STRING_INFO(INTERNAL_EventPipeOutputFile, W("EventPipeOutputFile"), "The full path including file name for the trace file that will be written when COMPlus_EnableEventPipe&=1")
 RETAIL_CONFIG_STRING_INFO(INTERNAL_EventPipeConfig, W("EventPipeConfig"), "Configuration for EventPipe.")
 RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeRundown, W("EventPipeRundown"), 1, "Enable/disable eventpipe rundown.")
+RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeCircularMB, W("EventPipeCircularMB"), 1024, "The EventPipe circular buffer size in megabytes.")
 
 #ifdef FEATURE_GDBJIT
 ///
index 83f1b23..885a524 100644 (file)
@@ -983,11 +983,6 @@ void EEStartupHelper(COINITIEE fFlags)
         SystemDomain::System()->PublishAppDomainAndInformDebugger(SystemDomain::System()->DefaultDomain());
 #endif
 
-#ifdef FEATURE_PERFTRACING
-        // Start the event pipe if requested.
-        EventPipe::EnableOnStartup();
-#endif // FEATURE_PERFTRACING
-
 #endif // CROSSGEN_COMPILE
 
         SystemDomain::System()->Init();
index 42eea42..df6728e 100644 (file)
@@ -203,37 +203,6 @@ void EventPipe::Initialize()
     InitProvidersAndEvents();
 }
 
-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_EnableEventPipe) & 1) == 1)
-    {
-        SString outputPath;
-        outputPath.Printf("Process-%d.netperf", GetCurrentProcessId());
-
-        // Create a new session.
-        EventPipeSession *pSession = new EventPipeSession(
-            EventPipeSessionType::File,
-            1024 /* 1 GB circular buffer */,
-            NULL, /* pProviders */
-            0 /* numProviders */);
-
-        // Get the configuration from the environment.
-        GetConfigurationFromEnvironment(outputPath, pSession);
-
-        // Enable the session.
-        Enable(outputPath, pSession);
-    }
-}
-
 void EventPipe::Shutdown()
 {
     CONTRACTL
index d5273c4..1982130 100644 (file)
@@ -234,9 +234,6 @@ class EventPipe
         // 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 EventPipeSessionID Enable(
             LPCWSTR strOutputPath,
index 3e11983..be83f1c 100644 (file)
@@ -1642,81 +1642,6 @@ void BulkTypeEventLogger::FireBulkTypeEvent()
     }
     UINT16 nClrInstanceID = GetClrInstanceId();
 
-#if !defined(FEATURE_PAL)
-    // Normally, we'd use the MC-generated FireEtwBulkType for all this gunk, but
-    // it's insufficient as the bulk type event is too complex (arrays of structs of
-    // varying size). So we directly log the event via EventDataDescCreate and
-    // EventWrite
-
-    // We use one descriptor for the count + one for the ClrInstanceID + 4
-    // per batched type (to include fixed-size data + name + param count + param
-    // array).  But the system limit of 128 descriptors per event kicks in way
-    // before the 64K event size limit, and we already limit our batch size
-    // (m_nBulkTypeValueCount) to stay within the 128 descriptor limit.
-    EVENT_DATA_DESCRIPTOR EventData[128];
-
-    UINT iDesc = 0;
-
-    _ASSERTE(iDesc < _countof(EventData));
-    EventDataDescCreate(&EventData[iDesc++], &m_nBulkTypeValueCount, sizeof(m_nBulkTypeValueCount));
-
-    _ASSERTE(iDesc < _countof(EventData));
-    EventDataDescCreate(&EventData[iDesc++], &nClrInstanceID, sizeof(nClrInstanceID));
-
-    for (int iTypeData = 0; iTypeData < m_nBulkTypeValueCount; iTypeData++)
-    {
-        // Do fixed-size data as one bulk copy
-        _ASSERTE(iDesc < _countof(EventData));
-        EventDataDescCreate(
-            &EventData[iDesc++], 
-            &(m_rgBulkTypeValues[iTypeData].fixedSizedData), 
-            sizeof(m_rgBulkTypeValues[iTypeData].fixedSizedData));
-
-        // Do var-sized data individually per field
-
-        // Type name (nonexistent and thus empty on FEATURE_REDHAWK)
-        _ASSERTE(iDesc < _countof(EventData));
-#ifdef FEATURE_REDHAWK
-        EventDataDescCreate(&EventData[iDesc++], W(""), sizeof(WCHAR));
-#else   // FEATURE_REDHAWK
-        LPCWSTR wszName = m_rgBulkTypeValues[iTypeData].sName.GetUnicode();
-        EventDataDescCreate(
-            &EventData[iDesc++], 
-            (wszName == NULL) ? W("") : wszName,
-            (wszName == NULL) ? sizeof(WCHAR) : (m_rgBulkTypeValues[iTypeData].sName.GetCount() + 1) * sizeof(WCHAR));
-#endif // FEATURE_REDHAWK
-
-        // Type parameter count
-#ifndef FEATURE_REDHAWK
-        m_rgBulkTypeValues[iTypeData].cTypeParameters = m_rgBulkTypeValues[iTypeData].rgTypeParameters.GetCount();
-#endif // FEATURE_REDHAWK
-        _ASSERTE(iDesc < _countof(EventData));
-        EventDataDescCreate(
-            &EventData[iDesc++], 
-            &(m_rgBulkTypeValues[iTypeData].cTypeParameters),
-            sizeof(m_rgBulkTypeValues[iTypeData].cTypeParameters));
-
-        // Type parameter array
-        if (m_rgBulkTypeValues[iTypeData].cTypeParameters > 0)
-        {
-            _ASSERTE(iDesc < _countof(EventData));
-            EventDataDescCreate(
-                &EventData[iDesc++], 
-#ifdef FEATURE_REDHAWK
-                ((m_rgBulkTypeValues[iTypeData].cTypeParameters == 1) ?
-                    &(m_rgBulkTypeValues[iTypeData].ullSingleTypeParameter) :
-                    (ULONGLONG *) (m_rgBulkTypeValues[iTypeData].rgTypeParameters)),
-#else
-                m_rgBulkTypeValues[iTypeData].rgTypeParameters.GetElements(),
-#endif
-                sizeof(ULONGLONG) * m_rgBulkTypeValues[iTypeData].cTypeParameters);
-        }
-    }
-
-    Win32EventWrite(Microsoft_Windows_DotNETRuntimeHandle, &BulkType, iDesc, EventData);
-    
-#else // FEATURE_PAL
-
     if(m_pBulkTypeEventBuffer == NULL)
     {
         // The buffer could not be allocated when this object was created, so bail.
@@ -1770,7 +1695,6 @@ void BulkTypeEventLogger::FireBulkTypeEvent()
 
     FireEtwBulkType(m_nBulkTypeValueCount, GetClrInstanceId(), iSize, m_pBulkTypeEventBuffer);
 
-#endif // FEATURE_PAL
     // Reset state
     m_nBulkTypeValueCount = 0;
     m_nBulkTypeValueByteCount = 0;
@@ -4249,6 +4173,13 @@ void InitializeEventTracing()
 // a suitable token, this implementation has a different callback for every EventPipe provider
 // that ultimately funnels them all into a common handler.
 
+#if defined(FEATURE_PAL)
+// CLR_GCHEAPCOLLECT_KEYWORD is defined by the generated ETW manifest on Windows.
+// On non-Windows, we need to make sure that this is defined.  Given that we can't change
+// the value due to compatibility, we specify it here rather than generating defines based on the manifest.
+#define CLR_GCHEAPCOLLECT_KEYWORD 0x800000
+#endif // defined(FEATURE_PAL)
+
 // CallbackProviderIndex provides a quick identification of which provider triggered the
 // ETW callback.
 enum CallbackProviderIndex
@@ -4266,7 +4197,8 @@ VOID EtwCallbackCommon(
     CallbackProviderIndex ProviderIndex,
     ULONG ControlCode,
     UCHAR Level,
-    ULONGLONG MatchAnyKeyword)
+    ULONGLONG MatchAnyKeyword,
+    PVOID pFilterData)
 {
     LIMITED_METHOD_CONTRACT;
 
@@ -4282,6 +4214,26 @@ VOID EtwCallbackCommon(
     GCEventKeyword keywords = static_cast<GCEventKeyword>(MatchAnyKeyword);
     GCEventLevel level = static_cast<GCEventLevel>(Level);
     GCHeapUtilities::RecordEventStateChange(bIsPublicTraceHandle, keywords, level);
+
+    // Special check for the runtime provider's GCHeapCollectKeyword.  Profilers
+    // flick this to force a full GC.
+    if (g_fEEStarted && !g_fEEShutDown && bIsPublicTraceHandle &&
+        ((MatchAnyKeyword & CLR_GCHEAPCOLLECT_KEYWORD) != 0))
+    {
+        // Profilers may (optionally) specify extra data in the filter parameter
+        // to log with the GCStart event.
+        LONGLONG l64ClientSequenceNumber = 0;
+#if !defined(FEATURE_PAL)
+        PEVENT_FILTER_DESCRIPTOR FilterData = (PEVENT_FILTER_DESCRIPTOR)pFilterData;
+        if ((FilterData != NULL) &&
+           (FilterData->Type == 1) &&
+           (FilterData->Size == sizeof(l64ClientSequenceNumber)))
+        {
+            l64ClientSequenceNumber = *(LONGLONG *) (FilterData->Ptr);
+        }
+#endif // !defined(FEATURE_PAL)
+        ETW::GCLog::ForceGC(l64ClientSequenceNumber);
+    }
 }
 
 // Individual callbacks for each EventPipe provider.
@@ -4297,7 +4249,7 @@ VOID EventPipeEtwCallbackDotNETRuntimeStress(
 {
     LIMITED_METHOD_CONTRACT;
 
-    EtwCallbackCommon(DotNETRuntimeStress, ControlCode, Level, MatchAnyKeyword);
+    EtwCallbackCommon(DotNETRuntimeStress, ControlCode, Level, MatchAnyKeyword, FilterData);
 }
 
 VOID EventPipeEtwCallbackDotNETRuntime(
@@ -4311,7 +4263,7 @@ VOID EventPipeEtwCallbackDotNETRuntime(
 {
     LIMITED_METHOD_CONTRACT;
 
-    EtwCallbackCommon(DotNETRuntime, ControlCode, Level, MatchAnyKeyword);
+    EtwCallbackCommon(DotNETRuntime, ControlCode, Level, MatchAnyKeyword, FilterData);
 }
 
 VOID EventPipeEtwCallbackDotNETRuntimeRundown(
@@ -4325,7 +4277,7 @@ VOID EventPipeEtwCallbackDotNETRuntimeRundown(
 {
     LIMITED_METHOD_CONTRACT;
 
-    EtwCallbackCommon(DotNETRuntimeRundown, ControlCode, Level, MatchAnyKeyword);
+    EtwCallbackCommon(DotNETRuntimeRundown, ControlCode, Level, MatchAnyKeyword, FilterData);
 }
 
 VOID EventPipeEtwCallbackDotNETRuntimePrivate(
@@ -4339,7 +4291,7 @@ VOID EventPipeEtwCallbackDotNETRuntimePrivate(
 {
     WRAPPER_NO_CONTRACT;
 
-    EtwCallbackCommon(DotNETRuntimePrivate, ControlCode, Level, MatchAnyKeyword);
+    EtwCallbackCommon(DotNETRuntimePrivate, ControlCode, Level, MatchAnyKeyword, FilterData);
 }
 
 
@@ -4491,7 +4443,7 @@ extern "C"
             return;
         }
 
-        EtwCallbackCommon(providerIndex, ControlCode, Level, MatchAnyKeyword);
+        EtwCallbackCommon(providerIndex, ControlCode, Level, MatchAnyKeyword, FilterData);
 
         // TypeSystemLog needs a notification when certain keywords are modified, so
         // give it a hook here.
@@ -4551,23 +4503,6 @@ extern "C"
             {
                 ETW::EnumerationLog::EnumerateForCaptureState();
             }
-
-            // Special check for the runtime provider's GCHeapCollectKeyword.  Profilers
-            // flick this to force a full GC.
-            if (g_fEEStarted && !g_fEEShutDown && bIsPublicTraceHandle &&
-                ((MatchAnyKeyword & CLR_GCHEAPCOLLECT_KEYWORD) != 0))
-            {
-                // Profilers may (optionally) specify extra data in the filter parameter
-                // to log with the GCStart event.
-                LONGLONG l64ClientSequenceNumber = 0;
-                if ((FilterData != NULL) &&
-                    (FilterData->Type == 1) &&
-                    (FilterData->Size == sizeof(l64ClientSequenceNumber)))
-                {
-                    l64ClientSequenceNumber = *(LONGLONG *) (FilterData->Ptr);
-                }
-                ETW::GCLog::ForceGC(l64ClientSequenceNumber);
-            }
         }
 #ifdef FEATURE_COMINTEROP
         if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, CCWRefCountChange)) 
index ae4f1c5..8e06daf 100644 (file)
@@ -254,10 +254,8 @@ class BulkTypeEventLogger
 {
 private:
 
-#ifdef FEATURE_PAL
     // The maximum event size, and the size of the buffer that we allocate to hold the event contents.
     static const size_t kSizeOfEventBuffer = 65536;
-#endif
 
     // Estimate of how many bytes we can squeeze in the event data for the value struct
     // array.  (Intentionally overestimate the size of the non-array parts to keep it safe.)
@@ -299,9 +297,7 @@ private:
     // List of types we've batched.
     BulkTypeValue m_rgBulkTypeValues[kMaxCountTypeValues];
 
-#ifdef FEATURE_PAL
     BYTE *m_pBulkTypeEventBuffer;
-#endif
 
 #ifdef FEATURE_REDHAWK
     int LogSingleType(EEType * pEEType);
@@ -313,9 +309,7 @@ public:
     BulkTypeEventLogger() :
         m_nBulkTypeValueCount(0),
         m_nBulkTypeValueByteCount(0)
-#ifdef FEATURE_PAL
         , m_pBulkTypeEventBuffer(NULL)
-#endif
     {
         CONTRACTL
         {
@@ -325,12 +319,9 @@ public:
         }
         CONTRACTL_END;
 
-#ifdef FEATURE_PAL
         m_pBulkTypeEventBuffer = new (nothrow) BYTE[kSizeOfEventBuffer];
-#endif
     }
 
-#ifdef FEATURE_PAL
     ~BulkTypeEventLogger()
     {
         CONTRACTL
@@ -344,7 +335,6 @@ public:
         delete[] m_pBulkTypeEventBuffer;
         m_pBulkTypeEventBuffer = NULL;
     }
-#endif
 
     void LogTypeAndParameters(ULONGLONG thAsAddr, ETW::TypeSystemLog::TypeLogBehavior typeLogBehavior);
     void FireBulkTypeEvent();