diff options
author | Brian Robbins <brianrob@microsoft.com> | 2018-10-05 16:44:15 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-05 16:44:15 -0700 |
commit | 457148064c836daa746542f1559b7958d6420c22 (patch) | |
tree | 637c49662f44b301afc0f3450055a7c72fd00119 /src/System.Private.CoreLib | |
parent | 27c848e37e9998142b60e776cf5b5d08a3543fe1 (diff) | |
download | coreclr-457148064c836daa746542f1559b7958d6420c22.tar.gz coreclr-457148064c836daa746542f1559b7958d6420c22.tar.bz2 coreclr-457148064c836daa746542f1559b7958d6420c22.zip |
Enable Config-File Based Control of EventPipe (#20238)
Diffstat (limited to 'src/System.Private.CoreLib')
3 files changed, 213 insertions, 103 deletions
diff --git a/src/System.Private.CoreLib/src/System/AppDomain.cs b/src/System.Private.CoreLib/src/System/AppDomain.cs index 45ddeaae97..be1330be2c 100644 --- a/src/System.Private.CoreLib/src/System/AppDomain.cs +++ b/src/System.Private.CoreLib/src/System/AppDomain.cs @@ -656,8 +656,6 @@ namespace System SetupFusionStore(new AppDomainSetup(), null); } } - - System.Diagnostics.Tracing.EventPipeController.Initialize(); } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs index 8a9a54be7f..39e67660a2 100644 --- a/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs +++ b/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeController.cs @@ -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; } } diff --git a/src/System.Private.CoreLib/src/System/StartupHookProvider.cs b/src/System.Private.CoreLib/src/System/StartupHookProvider.cs index cc6f8d41fd..ded4b2bfac 100644 --- a/src/System.Private.CoreLib/src/System/StartupHookProvider.cs +++ b/src/System.Private.CoreLib/src/System/StartupHookProvider.cs @@ -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) { |