diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/AppDomain.cs b/src/coreclr/src/System.Private.CoreLib/src/System/AppDomain.cs index 45ddeaae97583..be1330be2c1ab 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/AppDomain.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/AppDomain.cs @@ -656,8 +656,6 @@ private void SetupDomain(bool allowRedirects, string path, string configFile, st SetupFusionStore(new AppDomainSetup(), null); } } - - System.Diagnostics.Tracing.EventPipeController.Initialize(); } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs index 8a9a54be7f00a..39e67660a2064 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs @@ -6,7 +6,9 @@ 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 @@ internal sealed class EventPipeController 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 @@ internal static void Initialize() 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 @@ 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 + // 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 @@ private void PollForTracingCommand(object state) 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 @@ private static EventPipeConfiguration GetConfiguration() return config; } - /// - /// 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. - /// - 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 @@ private static uint Config_EventPipeCircularMB } } - 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; } } diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/StartupHookProvider.cs b/src/coreclr/src/System.Private.CoreLib/src/System/StartupHookProvider.cs index cc6f8d41fdac9..ded4b2bface6d 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/StartupHookProvider.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/StartupHookProvider.cs @@ -21,6 +21,9 @@ internal static class StartupHookProvider // 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) { diff --git a/src/coreclr/src/inc/clrconfigvalues.h b/src/coreclr/src/inc/clrconfigvalues.h index 58c3113cd30e8..79f475c7b9ed2 100644 --- a/src/coreclr/src/inc/clrconfigvalues.h +++ b/src/coreclr/src/inc/clrconfigvalues.h @@ -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.") diff --git a/src/coreclr/src/vm/eventpipe.cpp b/src/coreclr/src/vm/eventpipe.cpp index df6728e0460a8..882fad5c0cba9 100644 --- a/src/coreclr/src/vm/eventpipe.cpp +++ b/src/coreclr/src/vm/eventpipe.cpp @@ -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 diff --git a/src/coreclr/src/vm/eventpipe.h b/src/coreclr/src/vm/eventpipe.h index 1982130a3e162..cc4c01243449c 100644 --- a/src/coreclr/src/vm/eventpipe.h +++ b/src/coreclr/src/vm/eventpipe.h @@ -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); diff --git a/src/coreclr/src/vm/eventpipesession.cpp b/src/coreclr/src/vm/eventpipesession.cpp index b10caf2f5b4e1..2dd1a3f38df73 100644 --- a/src/coreclr/src/vm/eventpipesession.cpp +++ b/src/coreclr/src/vm/eventpipesession.cpp @@ -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; iGetProviderName(), - pConfig->GetKeywords(), - (EventPipeEventLevel)pConfig->GetLevel()); - m_pProviders->InsertTail(new SListElem(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(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) { diff --git a/src/coreclr/src/vm/eventpipesession.h b/src/coreclr/src/vm/eventpipesession.h index 5518e76097c6b..2880677fb6f30 100644 --- a/src/coreclr/src/vm/eventpipesession.h +++ b/src/coreclr/src/vm/eventpipesession.h @@ -96,10 +96,6 @@ class EventPipeSession 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 @@ class EventPipeSessionProviderList // The list of providers. SList> *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 @@ class EventPipeSessionProviderList 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); diff --git a/src/coreclr/tests/issues.targets b/src/coreclr/tests/issues.targets index 53eed75165e8e..d49472f9e9726 100644 --- a/src/coreclr/tests/issues.targets +++ b/src/coreclr/tests/issues.targets @@ -245,6 +245,12 @@ + + + Unable to write config file to app location + + + diff --git a/src/coreclr/tests/src/tracing/tracecontrol/TraceControl.cs b/src/coreclr/tests/src/tracing/tracecontrol/TraceControl.cs new file mode 100644 index 0000000000000..fbdc97abf893e --- /dev/null +++ b/src/coreclr/tests/src/tracing/tracecontrol/TraceControl.cs @@ -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; + + /// + /// This test collects a trace of itself and then performs some basic validation on the trace. + /// + 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/src/coreclr/tests/src/tracing/tracecontrol/tracecontrol.csproj b/src/coreclr/tests/src/tracing/tracecontrol/tracecontrol.csproj new file mode 100644 index 0000000000000..dca25a2e344d0 --- /dev/null +++ b/src/coreclr/tests/src/tracing/tracecontrol/tracecontrol.csproj @@ -0,0 +1,29 @@ + + + + + Debug + AnyCPU + 2.0 + {8E3244CB-407F-4142-BAAB-E7A55901A5FA} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + BuildAndRun + $(DefineConstants);STATIC + 0 + + + + + + + False + + + + + + + +