Enable Config-File Based Control of EventPipe (#20238)
authorBrian Robbins <brianrob@microsoft.com>
Fri, 5 Oct 2018 23:44:15 +0000 (16:44 -0700)
committerGitHub <noreply@github.com>
Fri, 5 Oct 2018 23:44:15 +0000 (16:44 -0700)
src/System.Private.CoreLib/src/System/AppDomain.cs
src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs
src/System.Private.CoreLib/src/System/StartupHookProvider.cs
src/inc/clrconfigvalues.h
src/vm/eventpipe.cpp
src/vm/eventpipe.h
src/vm/eventpipesession.cpp
src/vm/eventpipesession.h
tests/issues.targets
tests/src/tracing/tracecontrol/TraceControl.cs [new file with mode: 0644]
tests/src/tracing/tracecontrol/tracecontrol.csproj [new file with mode: 0644]

index 45ddeaa..be1330b 100644 (file)
@@ -656,8 +656,6 @@ namespace System
                     SetupFusionStore(new AppDomainSetup(), null);
                 }
             }
-
-            System.Diagnostics.Tracing.EventPipeController.Initialize();
         }
 
         [MethodImpl(MethodImplOptions.InternalCall)]
index 8a9a54b..39e6766 100644 (file)
@@ -6,7 +6,9 @@ using Internal.IO;
 using Microsoft.Win32;
 using System.IO;
 using System.Globalization;
+using System.Reflection;
 using System.Runtime.Versioning;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -31,13 +33,22 @@ namespace System.Diagnostics.Tracing
     internal sealed class EventPipeController
     {
         // Miscellaneous constants.
+        private const string DefaultAppName = "app";
         private const string NetPerfFileExtension = ".netperf";
-        private const string MarkerFileExtension = ".ctl";
+        private const string ConfigFileSuffix = ".eventpipeconfig";
         private const int EnabledPollingIntervalMilliseconds = 1000; // 1 second
-        private const int DisabledPollingIntervalMilliseconds = 10000; // 10 seconds
+        private const int DisabledPollingIntervalMilliseconds = 5000; // 5 seconds
         private const uint DefaultCircularBufferMB = 1024; // 1 GB
         private static readonly char[] ProviderConfigDelimiter = new char[] { ',' };
         private static readonly char[] ConfigComponentDelimiter = new char[] { ':' };
+        private static readonly string[] ConfigFileLineDelimiters = new string[] { "\r\n", "\n" };
+        private const char ConfigEntryDelimiter = '=';
+
+        // Config file keys.
+        private const string ConfigKey_Providers = "Providers";
+        private const string ConfigKey_CircularMB = "CircularMB";
+        private const string ConfigKey_OutputPath = "OutputPath";
+        private const string ConfigKey_ProcessID = "ProcessID";
 
         // The default set of providers/keywords/levels.  Used if an alternative configuration is not specified.
         private static readonly EventPipeProviderConfiguration[] DefaultProviderConfiguration = new EventPipeProviderConfiguration[]
@@ -47,39 +58,33 @@ namespace System.Diagnostics.Tracing
             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_configFilePath;
+        private DateTime m_configFileUpdateTime;
         private string m_traceFilePath = null;
-        private string m_markerFilePath = null;
-        private bool m_markerFileExists = false;
+        private bool m_configFileExists = false;
 
         internal static void Initialize()
         {
-            // Don't allow failures to propagate upstream.
-            // Instead, ensure program correctness without tracing.
+            // Don't allow failures to propagate upstream.  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)
+                    if (Config_EnableEventPipe > 0)
                     {
                         // Enable tracing immediately.
                         // It will be disabled automatically on shutdown.
-                        EventPipe.Enable(GetConfiguration());
+                        EventPipe.Enable(BuildConfigFromEnvironment());
+                    }
+                    else
+                    {
+                        // Create a new controller to listen for commands.
+                        s_controllerInstance = new EventPipeController();
                     }
                 }
             }
@@ -88,15 +93,20 @@ namespace System.Diagnostics.Tracing
 
         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).
+            // Set the config file path.
+            m_configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, BuildConfigFileName());
+
+            // Initialize the timer, but don't set it to run.
+            // The timer will be set to run each time PollForTracingCommand is called.
             m_timer = new Timer(
                 callback: new TimerCallback(PollForTracingCommand),
                 state: null,
-                dueTime: DisabledPollingIntervalMilliseconds,
+                dueTime: Timeout.Infinite,
                 period: Timeout.Infinite,
                 flowExecutionContext: false);
+
+            // Trigger the first poll operation on the start-up path.
+            PollForTracingCommand(null);
         }
 
         private void PollForTracingCommand(object state)
@@ -104,32 +114,26 @@ namespace System.Diagnostics.Tracing
             // 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
+                // Check for existence of the config file.
+                // If the existence of the file has changed since the last time we checked or the update time has changed
                 // this means that we need to act on that change.
-                bool fileExists = File.Exists(m_markerFilePath);
-                if (m_markerFileExists != fileExists)
+                bool fileExists = File.Exists(m_configFilePath);
+                if (m_configFileExists != fileExists)
                 {
                     // Save the result.
-                    m_markerFileExists = fileExists;
+                    m_configFileExists = fileExists;
 
                     // Take the appropriate action.
                     if (fileExists)
                     {
                         // Enable tracing.
-                        EventPipe.Enable(GetConfiguration());
+                        // Check for null here because it's possible that the configuration contains a process filter
+                        // that doesn't match the current process.  IF this occurs, we should't enable tracing.
+                        EventPipeConfiguration config = BuildConfigFromFile(m_configFilePath);
+                        if (config != null)
+                        {
+                            EventPipe.Enable(config);
+                        }
                     }
                     else
                     {
@@ -144,45 +148,110 @@ namespace System.Diagnostics.Tracing
             catch { }
         }
 
-        private static EventPipeConfiguration GetConfiguration()
+        private static EventPipeConfiguration BuildConfigFromFile(string configFilePath)
+        {
+            // Read the config file in once call.
+            byte[] configContents = File.ReadAllBytes(configFilePath);
+
+            // Convert the contents to a string.
+            string strConfigContents = Encoding.UTF8.GetString(configContents);
+
+            // Read all of the config options.
+            string outputPath = null;
+            string strProviderConfig = null;
+            string strCircularMB = null;
+            string strProcessID = null;
+
+            // Split the configuration entries by line.
+            string[] configEntries = strConfigContents.Split(ConfigFileLineDelimiters, StringSplitOptions.RemoveEmptyEntries);
+            foreach (string configEntry in configEntries)
+            {
+                //`Split the key and value by '='.
+                string[] entryComponents = configEntry.Split(ConfigEntryDelimiter);
+                if(entryComponents.Length == 2)
+                {
+                    string key = entryComponents[0];
+                    if (key.Equals(ConfigKey_Providers))
+                    {
+                        strProviderConfig = entryComponents[1];
+                    }
+                    else if (key.Equals(ConfigKey_OutputPath))
+                    {
+                        outputPath = entryComponents[1];
+                    }
+                    else if (key.Equals(ConfigKey_CircularMB))
+                    {
+                        strCircularMB = entryComponents[1];
+                    }
+                    else if (key.Equals(ConfigKey_ProcessID))
+                    {
+                        strProcessID = entryComponents[1];
+                    }
+                }
+            }
+
+            // Check the process ID filter if it is set.
+            if (!string.IsNullOrEmpty(strProcessID))
+            {
+                // If set, bail out early if the specified process does not match the current process.
+                int processID = Convert.ToInt32(strProcessID);
+                if (processID != Win32Native.GetCurrentProcessId())
+                {
+                    return null;
+                }
+            }
+
+            // Ensure that the output path is set.
+            if (string.IsNullOrEmpty(outputPath))
+            {
+                throw new ArgumentNullException(nameof(outputPath));
+            }
+
+            // Build the full path to the trace file.
+            string traceFileName = BuildTraceFileName();
+            string outputFile = Path.Combine(outputPath, traceFileName);
+
+            // Get the circular buffer size.
+            uint circularMB = DefaultCircularBufferMB;
+            if(!string.IsNullOrEmpty(strCircularMB))
+            {
+                circularMB = Convert.ToUInt32(strCircularMB);
+            }
+
+            // Initialize a new configuration object.
+            EventPipeConfiguration config = new EventPipeConfiguration(outputFile, circularMB);
+
+            // Set the provider configuration if specified.
+            if (!string.IsNullOrEmpty(strProviderConfig))
+            {
+                SetProviderConfiguration(strProviderConfig, config);
+            }
+            else
+            {
+                // If the provider configuration isn't specified, use the default.
+                config.EnableProviderRange(DefaultProviderConfiguration);
+            }
+
+            return config;
+        }
+
+        private static EventPipeConfiguration BuildConfigFromEnvironment()
         {
+            // Build the full path to the trace file.
+            string traceFileName = BuildTraceFileName();
+            string outputFilePath = Path.Combine(Config_EventPipeOutputPath, traceFileName);
+
             // Create a new configuration object.
             EventPipeConfiguration config = new EventPipeConfiguration(
-                GetDisambiguatedTraceFilePath(Config_EventPipeOutputFile),
+                outputFilePath,
                 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);
-                    }
-                }
+                // If the configuration is specified, parse it and save it to the config object.
+                SetProviderConfiguration(strConfig, config);
             }
             else
             {
@@ -193,34 +262,82 @@ namespace System.Diagnostics.Tracing
             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)
+        private static string BuildConfigFileName()
+        {
+            return GetAppName() + ConfigFileSuffix;
+        }
+
+        private static string BuildTraceFileName()
         {
-            if (string.IsNullOrEmpty(inputPath))
+            return GetAppName() + "." + Win32Native.GetCurrentProcessId() + NetPerfFileExtension;
+        }
+
+        private static string GetAppName()
+        {
+            string appName = null;
+            Assembly entryAssembly = Assembly.GetEntryAssembly();
+            if (entryAssembly != null)
             {
-                throw new ArgumentNullException("inputPath");
+                AssemblyName assemblyName = entryAssembly.GetName();
+                if (assemblyName != null)
+                {
+                    appName = assemblyName.Name;
+                }
             }
 
-            string filePath = inputPath;
-            if (File.Exists(filePath))
+            if (string.IsNullOrEmpty(appName))
             {
-                string directoryName = Path.GetDirectoryName(filePath);
-                string fileWithoutExtension = Path.GetFileName(filePath);
-                string extension = Path.GetExtension(filePath);
+                appName = DefaultAppName;
+            }
+
+            return appName;
+        }
 
-                string newFileWithExtension = fileWithoutExtension + "." + Win32Native.GetCurrentProcessId() + extension;
-                filePath = Path.Combine(directoryName, newFileWithExtension);
+        private static void SetProviderConfiguration(string strConfig, EventPipeConfiguration config)
+        {
+            if (string.IsNullOrEmpty(strConfig))
+            {
+                throw new ArgumentNullException(nameof(strConfig));
             }
 
-            return filePath;
+            // 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);
+                }
+            }
         }
 
         #region Configuration
 
+        // 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_EventPipeOutputPath = null;
+
         private static int Config_EnableEventPipe
         {
             get
@@ -268,28 +385,20 @@ namespace System.Diagnostics.Tracing
             }
         }
 
-        private static string Config_EventPipeOutputFile
+        private static string Config_EventPipeOutputPath
         {
             get
             {
-                if (s_Config_EventPipeOutputFile == null)
+                if (s_Config_EventPipeOutputPath == null)
                 {
-                    s_Config_EventPipeOutputFile = CompatibilitySwitch.GetValueInternal("EventPipeOutputFile");
-                    if (s_Config_EventPipeOutputFile == null)
+                    s_Config_EventPipeOutputPath = CompatibilitySwitch.GetValueInternal("EventPipeOutputPath");
+                    if (s_Config_EventPipeOutputPath == null)
                     {
-                        s_Config_EventPipeOutputFile = "Process-" + Win32Native.GetCurrentProcessId() + NetPerfFileExtension;
+                        s_Config_EventPipeOutputPath = ".";
                     }
                 }
 
-                return s_Config_EventPipeOutputFile;
-            }
-        }
-
-        private static string MarkerFilePath
-        {
-            get
-            {
-                return Config_EventPipeOutputFile + MarkerFileExtension;
+                return s_Config_EventPipeOutputPath;
             }
         }
 
index cc6f8d4..ded4b2b 100644 (file)
@@ -21,6 +21,9 @@ namespace System
         // containing a startup hook, and call each hook in turn.
         private static void ProcessStartupHooks()
         {
+            // Initialize tracing before any user code can be called.
+            System.Diagnostics.Tracing.EventPipeController.Initialize();
+
             string startupHooksVariable = (string)AppContext.GetData("STARTUP_HOOKS");
             if (startupHooksVariable == null)
             {
index 58c3113..79f475c 100644 (file)
@@ -738,7 +738,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_AllowDComReflection, W("AllowDComReflection"),
 // EventPipe
 //
 RETAIL_CONFIG_DWORD_INFO(INTERNAL_EnableEventPipe, W("EnableEventPipe"), 0, "Enable/disable event pipe.  Non-zero values enable tracing.")
-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_EventPipeOutputPath, W("EventPipeOutputPath"), "The full path excluding 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.")
index df6728e..882fad5 100644 (file)
@@ -830,130 +830,6 @@ CrstStatic* EventPipe::GetLock()
     return &s_configCrst;
 }
 
-void EventPipe::GetConfigurationFromEnvironment(SString &outputPath, EventPipeSession *pSession)
-{
-    LIMITED_METHOD_CONTRACT;
-
-    // Set the output path if specified.
-    CLRConfigStringHolder wszOutputPath(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeOutputFile));
-    if(wszOutputPath != NULL)
-    {
-        outputPath.Set(wszOutputPath);
-    }
-
-    // Read the the provider configuration from the environment if specified.
-    CLRConfigStringHolder wszConfig(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeConfig));
-    if(wszConfig == NULL)
-    {
-        pSession->EnableAllEvents();
-        return;
-    }
-
-    size_t len = wcslen(wszConfig);
-    if(len <= 0)
-    {
-        pSession->EnableAllEvents();
-        return;
-    }
-
-    // Parses a string with the following format:
-    //
-    //      ProviderName:Keywords:Level[,]*
-    //
-    // For example:
-    //
-    //      Microsoft-Windows-DotNETRuntime:0xCAFEBABE:2,Microsoft-Windows-DotNETRuntimePrivate:0xDEADBEEF:1
-    //
-    // Each provider configuration is separated by a ',' and each component within the configuration is
-    // separated by a ':'.
-
-    const WCHAR ProviderSeparatorChar = ',';
-    const WCHAR ComponentSeparatorChar = ':';
-    size_t index = 0;
-    WCHAR *pProviderName = NULL;
-    UINT64 keywords = 0;
-    EventPipeEventLevel level = EventPipeEventLevel::Critical;
-
-    while(index < len)
-    {
-        WCHAR * pCurrentChunk = &wszConfig[index];
-        size_t currentChunkStartIndex = index;
-        size_t currentChunkEndIndex = 0;
-
-        // Find the next chunk.
-        while(index < len && wszConfig[index] != ProviderSeparatorChar)
-        {
-            index++;
-        }
-        currentChunkEndIndex = index++;
-
-        // Split the chunk into components.
-        size_t chunkIndex = currentChunkStartIndex;
-
-        // Get the provider name.
-        size_t provNameStartIndex = chunkIndex;
-        size_t provNameEndIndex = currentChunkEndIndex;
-
-        while(chunkIndex < currentChunkEndIndex && wszConfig[chunkIndex] != ComponentSeparatorChar)
-        {
-            chunkIndex++;
-        }
-        provNameEndIndex = chunkIndex++;
-
-        size_t provNameLen = provNameEndIndex - provNameStartIndex;
-        pProviderName = new WCHAR[provNameLen+1];
-        memcpy(pProviderName, &wszConfig[provNameStartIndex], provNameLen*sizeof(WCHAR));
-        pProviderName[provNameLen] = '\0';
-
-        // Get the keywords.
-        size_t keywordsStartIndex = chunkIndex;
-        size_t keywordsEndIndex = currentChunkEndIndex;
-
-        while(chunkIndex < currentChunkEndIndex && wszConfig[chunkIndex] != ComponentSeparatorChar)
-        {
-            chunkIndex++;
-        }
-        keywordsEndIndex = chunkIndex++;
-
-        size_t keywordsLen = keywordsEndIndex - keywordsStartIndex;
-        WCHAR *wszKeywords = new WCHAR[keywordsLen+1];
-        memcpy(wszKeywords, &wszConfig[keywordsStartIndex], keywordsLen*sizeof(WCHAR));
-        wszKeywords[keywordsLen] = '\0';
-        keywords = _wcstoui64(wszKeywords, NULL, 16);
-        delete[] wszKeywords;
-        wszKeywords = NULL;
-
-        // Get the level.
-        size_t levelStartIndex = chunkIndex;
-        size_t levelEndIndex = currentChunkEndIndex;
-
-        while(chunkIndex < currentChunkEndIndex && wszConfig[chunkIndex] != ComponentSeparatorChar)
-        {
-            chunkIndex++;
-        }
-        levelEndIndex = chunkIndex++;
-
-        size_t levelLen = levelEndIndex - levelStartIndex;
-        WCHAR *wszLevel = new WCHAR[levelLen+1];
-        memcpy(wszLevel, &wszConfig[levelStartIndex], levelLen*sizeof(WCHAR));
-        wszLevel[levelLen] = '\0';
-        level = (EventPipeEventLevel) wcstoul(wszLevel, NULL, 16);
-        delete[] wszLevel;
-        wszLevel = NULL;
-
-        // Add a new EventPipeSessionProvider.
-        EventPipeSessionProvider *pSessionProvider = new EventPipeSessionProvider(pProviderName, keywords, level);
-        pSession->AddSessionProvider(pSessionProvider);
-
-        // Free the provider name string.
-        if(pProviderName != NULL)
-        {
-            delete[] pProviderName;
-            pProviderName = NULL;
-        }
-    }
-}
-
 void EventPipe::SaveCommandLine(LPCWSTR pwzAssemblyPath, int argc, LPCWSTR *argv)
 {
     CONTRACTL
index 1982130..cc4c012 100644 (file)
@@ -292,10 +292,6 @@ class EventPipe
         // Enable the specified EventPipe session.
         static EventPipeSessionID Enable(LPCWSTR strOutputPath, EventPipeSession *pSession);
 
-        // Get the EnableOnStartup configuration from environment.
-        static void GetConfigurationFromEnvironment(SString &outputPath, EventPipeSession *pSession);
-
-
         // Callback function for the stack walker.  For each frame walked, this callback is invoked.
         static StackWalkAction StackWalkCallback(CrawlFrame *pCf, StackContents *pData);
 
index b10caf2..2dd1a3f 100644 (file)
@@ -75,19 +75,6 @@ void EventPipeSession::AddSessionProvider(EventPipeSessionProvider *pProvider)
     m_pProviderList->AddSessionProvider(pProvider);
 }
 
-void EventPipeSession::EnableAllEvents()
-{
-    CONTRACTL
-    {
-        THROWS;
-        GC_NOTRIGGER;
-        MODE_ANY;
-    }
-    CONTRACTL_END;
-
-    m_pProviderList->EnableAllEvents();
-}
-
 EventPipeSessionProvider* EventPipeSession::GetSessionProvider(EventPipeProvider *pProvider)
 {
     CONTRACTL
@@ -118,12 +105,21 @@ EventPipeSessionProviderList::EventPipeSessionProviderList(
     for(unsigned int i=0; i<numConfigs; i++)
     {
         EventPipeProviderConfiguration *pConfig = &pConfigs[i];
-        EventPipeSessionProvider *pProvider = new EventPipeSessionProvider(
-            pConfig->GetProviderName(),
-            pConfig->GetKeywords(),
-            (EventPipeEventLevel)pConfig->GetLevel());
 
-        m_pProviders->InsertTail(new SListElem<EventPipeSessionProvider*>(pProvider));
+        // Enable all events if the provider name == '*', all keywords are on and the requested level == verbose.
+        if((wcscmp(W("*"), pConfig->GetProviderName()) == 0) && (pConfig->GetKeywords() == 0xFFFFFFFFFFFFFFFF) && ((EventPipeEventLevel)pConfig->GetLevel() == EventPipeEventLevel::Verbose) && (m_pCatchAllProvider == NULL))
+        {
+            m_pCatchAllProvider = new EventPipeSessionProvider(NULL, 0xFFFFFFFFFFFFFFFF, EventPipeEventLevel::Verbose);
+        }
+        else
+        {
+            EventPipeSessionProvider *pProvider = new EventPipeSessionProvider(
+                pConfig->GetProviderName(),
+                pConfig->GetKeywords(),
+                (EventPipeEventLevel)pConfig->GetLevel());
+
+            m_pProviders->InsertTail(new SListElem<EventPipeSessionProvider*>(pProvider));
+        }
     }
 }
 
@@ -176,16 +172,6 @@ void EventPipeSessionProviderList::AddSessionProvider(EventPipeSessionProvider *
     }
 }
 
-void EventPipeSessionProviderList::EnableAllEvents()
-{
-    LIMITED_METHOD_CONTRACT;
-
-    if(m_pCatchAllProvider == NULL)
-    {
-        m_pCatchAllProvider = new EventPipeSessionProvider(NULL, 0xFFFFFFFFFFFFFFFF, EventPipeEventLevel::Verbose);
-    }
-}
-
 EventPipeSessionProvider* EventPipeSessionProviderList::GetSessionProvider(
     EventPipeProvider *pProvider)
 {
index 5518e76..2880677 100644 (file)
@@ -96,10 +96,6 @@ public:
         return m_sessionStartTimeStamp;
     }
 
-    // Enable all events.
-    // This is used for testing and is controlled via COMPLUS_EnableEventPipe.
-    void EnableAllEvents();
-
     // Add a new provider to the session.
     void AddSessionProvider(EventPipeSessionProvider *pProvider);
 
@@ -115,8 +111,7 @@ private:
     // The list of providers.
     SList<SListElem<EventPipeSessionProvider*>> *m_pProviders;
 
-    // A catch-all provider used when tracing is enabled at start-up
-    // under (COMPlus_PerformanceTracing & 1) == 1.
+    // A catch-all provider used when tracing is enabled for all events.
     EventPipeSessionProvider *m_pCatchAllProvider;
 
 public:
@@ -125,10 +120,6 @@ public:
     EventPipeSessionProviderList(EventPipeProviderConfiguration *pConfigs, unsigned int numConfigs);
     ~EventPipeSessionProviderList();
 
-    // Enable all events.
-    // This is used for testing and is controlled via COMPLUS_EnableEventPipe.
-    void EnableAllEvents();
-
     // Add a new session provider to the list.
     void AddSessionProvider(EventPipeSessionProvider *pProvider);
 
index 53eed75..d49472f 100644 (file)
         </ExcludeList>
     </ItemGroup>
 
+    <ItemGroup Condition="'$(XunitTestBinBase)' != '' and '$(TargetsWindows)' == 'true'">
+        <ExcludeList Include="$(XunitTestBinBase)/tracing/tracecontrol/tracecontrol/*">
+            <Issue>Unable to write config file to app location</Issue>
+        </ExcludeList>
+    </ItemGroup>
+
     <!-- Windows x64 specific excludes -->
     <ItemGroup Condition="'$(XunitTestBinBase)' != '' and '$(BuildArch)' == 'x64' and '$(TargetsWindows)' == 'true'">
         <ExcludeList Include="$(XunitTestBinBase)/Loader/classloader/TypeGeneratorTests/TypeGeneratorTest612/Generated612/*">
diff --git a/tests/src/tracing/tracecontrol/TraceControl.cs b/tests/src/tracing/tracecontrol/TraceControl.cs
new file mode 100644 (file)
index 0000000..fbdc97a
--- /dev/null
@@ -0,0 +1,113 @@
+// 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.
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Tracing.Tests.Common;
+
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing.Parsers.Clr;
+
+namespace Tracing.Tests
+{
+    public static class TraceControlTest
+    {
+        private static string ConfigFileContents = @"
+OutputPath=.
+CircularMB=2048
+Providers=*:0xFFFFFFFFFFFFFFFF:5
+";
+
+        private const int BytesInOneMB = 1024 * 1024;
+
+        /// <summary>
+        /// This test collects a trace of itself and then performs some basic validation on the trace.
+        /// </summary>
+        public static int Main(string[] args)
+        {
+            // Calculate the path to the config file.
+            string configFileName = Assembly.GetEntryAssembly().GetName().Name + ".eventpipeconfig";
+            string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, configFileName);
+            Console.WriteLine("Calculated config file path: " + configFilePath);
+
+            // Write the config file to disk.
+            File.WriteAllText(configFilePath, ConfigFileContents);
+            Console.WriteLine("Wrote contents of config file.");
+
+            // Wait 5 seconds to ensure that tracing has started.
+            Console.WriteLine("Waiting 5 seconds for the config file to be picked up by the next poll operation.");
+            Thread.Sleep(TimeSpan.FromSeconds(5));
+
+            // Do some work that we can look for in the trace.
+            Console.WriteLine("Do some work that will be captured by the trace.");
+            GC.Collect(2, GCCollectionMode.Forced);
+            Console.WriteLine("Done with the work.");
+
+            // Delete the config file to start tracing.
+            File.Delete(configFilePath);
+            Console.WriteLine("Deleted the config file.");
+
+            // Build the full path to the trace file.
+            string[] traceFiles = Directory.GetFiles(".", "*.netperf", SearchOption.TopDirectoryOnly);
+            Assert.Equal("traceFiles.Length == 1", traceFiles.Length, 1);
+            string traceFilePath = traceFiles[0];
+
+            // Poll the file system and wait for the trace file to be written.
+            Console.WriteLine("Wait for the config file deletion to be picked up and for the trace file to be written.");
+
+            // Wait for 1 second, which is the poll time when tracing is enabled.
+            Thread.Sleep(TimeSpan.FromSeconds(1));
+
+            // Poll for file size changes to the trace file itself.  When the size of the trace file hasn't changed for 5 seconds, consider it fully written out.
+            Console.WriteLine("Waiting for the trace file to be written.  Poll every second to watch for 5 seconds of no file size changes.");
+            long lastSizeInBytes = 0;
+            DateTime timeOfLastChangeUTC = DateTime.UtcNow;
+            do
+            {
+                FileInfo traceFileInfo = new FileInfo(traceFilePath);
+                long currentSizeInBytes = traceFileInfo.Length;
+                Console.WriteLine("Trace file size: " + ((double)currentSizeInBytes / BytesInOneMB));
+
+                if (currentSizeInBytes > lastSizeInBytes)
+                {
+                    lastSizeInBytes = currentSizeInBytes;
+                    timeOfLastChangeUTC = DateTime.UtcNow;
+                }
+
+                Thread.Sleep(TimeSpan.FromSeconds(1));
+
+            } while (DateTime.UtcNow.Subtract(timeOfLastChangeUTC) < TimeSpan.FromSeconds(5));
+
+            int retVal = 0;
+
+            // Use TraceEvent to consume the trace file and look for the work that we did.
+            Console.WriteLine("Using TraceEvent to parse the file to find the work that was done during trace capture.");
+            using (var trace = TraceEventDispatcher.GetDispatcherFromFileName(traceFilePath))
+            {
+                string gcReasonInduced = GCReason.Induced.ToString();
+                string providerName = "Microsoft-Windows-DotNETRuntime";
+                string gcTriggeredEventName = "GC/Triggered";
+
+                trace.Clr.GCTriggered += delegate (GCTriggeredTraceData data)
+                {
+                    if (gcReasonInduced.Equals(data.Reason.ToString()))
+                    {
+                        Console.WriteLine("Detected an induced GC");
+                        retVal = 100;
+                    }
+                };
+
+                trace.Process();
+            }
+
+            // Clean-up the resulting trace file.
+            File.Delete(traceFilePath);
+
+            return retVal;
+        }
+    }
+}
diff --git a/tests/src/tracing/tracecontrol/tracecontrol.csproj b/tests/src/tracing/tracecontrol/tracecontrol.csproj
new file mode 100644 (file)
index 0000000..dca25a2
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{8E3244CB-407F-4142-BAAB-E7A55901A5FA}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+    <CLRTestPriority>0</CLRTestPriority>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"></PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="TraceControl.cs" />
+    <ProjectReference Include="../common/common.csproj" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>