diff --git a/src/PerfView/CommandLineArgs.cs b/src/PerfView/CommandLineArgs.cs index d41dc49da..dbc0018fa 100644 --- a/src/PerfView/CommandLineArgs.cs +++ b/src/PerfView/CommandLineArgs.cs @@ -210,6 +210,7 @@ public bool ShouldZip public DateTime EndTime; public bool ForceNgenRundown; public bool DumpHeap; + public bool DisableDotNetVersionLogging; // Collect options public bool NoGui; @@ -561,6 +562,8 @@ private void SetupCommandLine(CommandLineParser parser) "Displays the hexadecimal address rather than ? when the address is unknown."); parser.DefineOptionalQualifier("ShowOptimizationTiers", ref ShowOptimizationTiers, "Displays the optimization tier of each code version executed for the method."); + parser.DefineOptionalQualifier("DisableDotNetVersionLogging", ref DisableDotNetVersionLogging, + "Disables capturing of .NET version information during collection."); parser.DefineOptionalQualifier("NoGui", ref NoGui, "Use the Command line version of the command (like on ARM). Brings up a console window. For batch scripts/automation use /LogFile instead (see users guide under 'Scripting' for more)."); parser.DefineOptionalQualifier("SafeMode", ref SafeMode, "Turn off parallelism and other risky features."); diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index abcebfcae..3bc34fd33 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -109,6 +109,8 @@ public void Run(CommandLineArgs parsedArgs) Command cmd = null; try { + DotNetVersionLogger.Start(); + Start(parsedArgs); Thread.Sleep(100); // Allow time for the start rundown events OS events to happen. DateTime startTime = DateTime.Now; @@ -153,6 +155,7 @@ public void Run(CommandLineArgs parsedArgs) } finally { + DotNetVersionLogger.Stop(); if (!success) { if (cmd != null) @@ -205,6 +208,7 @@ public void Collect(CommandLineArgs parsedArgs) } else { + DotNetVersionLogger.Start(); Start(parsedArgs); WaitUntilCollectionDone(collectionCompleted, parsedArgs, DateTime.Now); if (m_aborted) @@ -218,6 +222,7 @@ public void Collect(CommandLineArgs parsedArgs) } finally { + DotNetVersionLogger.Stop(); collectionCompleted.Set(); // This ensures that the GUI window closes. if (!success) { @@ -2984,6 +2989,11 @@ public static string ParsedArgsAsString(string command, CommandLineArgs parsedAr cmdLineArgs += " /ShowOptimizationTiers"; } + if (parsedArgs.DisableDotNetVersionLogging) + { + cmdLineArgs += " /DisableVersionLogging"; + } + if (parsedArgs.ContinueOnError) { cmdLineArgs += " /ContinueOnError"; @@ -3445,6 +3455,12 @@ private void DoClrRundownForSession(string fileName, string sessionName, Command try { Stopwatch sw = Stopwatch.StartNew(); + if (!DotNetVersionLogger.Running) + { + DotNetVersionLogger.Start(); + } + + DotNetVersionLogger.StartRundown(); var rundownFile = Path.ChangeExtension(fileName, ".clrRundown.etl"); using (TraceEventSession clrRundownSession = new TraceEventSession(sessionName + "Rundown", rundownFile)) { @@ -3582,6 +3598,7 @@ private void DoClrRundownForSession(string fileName, string sessionName, Command WaitForRundownIdle(parsedArgs.MinRundownTime, parsedArgs.RundownTimeout, rundownFile); // Complete perfview rundown. + DotNetVersionLogger.Stop(); PerfViewLogger.Log.CommandLineParameters(ParsedArgsAsString(null, parsedArgs), Environment.CurrentDirectory, AppInfo.VersionNumber); PerfViewLogger.Log.StartAndStopTimes(); PerfViewLogger.Log.StopRundown(); diff --git a/src/PerfView/DotNetVersionLogger.cs b/src/PerfView/DotNetVersionLogger.cs new file mode 100644 index 000000000..1201012aa --- /dev/null +++ b/src/PerfView/DotNetVersionLogger.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Tracing.Parsers; +using Microsoft.Diagnostics.Tracing.Parsers.Clr; +using Microsoft.Diagnostics.Tracing.Session; + +namespace PerfView +{ + /// + /// Monitors for Runtime/Start events from the Microsoft-Windows-DotNETRuntime and Microsoft-Windows-DotNETRuntimeRundown providers + /// and logs the version information for the associated runtime DLLs. + /// + internal static class DotNetVersionLogger + { + private static VersionLogger _loggerInstance; + + private sealed class VersionLogger : IDisposable + { + private const string SessionName = "PerfView-DotNetVersionLogger-Session"; + private readonly static TextWriter Log = App.CommandProcessor.LogFile; + private TraceEventSession _session; + private AutoResetEvent _sessionStopEvent = new AutoResetEvent(false); + private HashSet _loggedPaths = new HashSet(); + + public void Dispose() + { + if (_session != null) + { + _session.Dispose(); + _session = null; + } + } + + public void Start() + { + try + { + _session = new TraceEventSession(SessionName); + _session.EnableProvider( + ClrTraceEventParser.ProviderGuid, + TraceEventLevel.Always, + (ulong)TraceEventKeyword.None, + new TraceEventProviderOptions() { EventIDsToEnable = new List { 187 } }); + + _session.Source.Clr.RuntimeStart += OnRuntimeInformationStartEvent; + ClrRundownTraceEventParser rundownParser = new ClrRundownTraceEventParser(_session.Source); + rundownParser.RuntimeStart += OnRuntimeInformationStartEvent; + + Task.Factory.StartNew(() => + { + _session.Source.Process(); + _sessionStopEvent.Set(); + }); + } + catch (Exception ex) + { + Log.WriteLine($"Failed to start dotnet version tracking: {ex}"); + } + } + + public void StartRundown() + { + try + { + _session.EnableProvider( + ClrRundownTraceEventParser.ProviderGuid, + TraceEventLevel.Always, + (ulong)TraceEventKeyword.None, + new TraceEventProviderOptions() { EventIDsToEnable = new List { 187 } }); + } + catch (Exception ex) + { + Log.WriteLine($"Failed to enable rundown provider: {ex}"); + } + } + + public void Stop() + { + if (_session != null) + { + _session.Dispose(); + _session = null; + + // Wait for the session to stop. + _sessionStopEvent.WaitOne(); + + try + { + foreach (string dllPath in _loggedPaths) + { + if (File.Exists(dllPath)) + { + FileVersionInfo info = FileVersionInfo.GetVersionInfo(dllPath); + PerfViewLogger.Log.RuntimeVersion(dllPath, info.FileVersion); + } + } + } + catch (Exception ex) + { + Log.WriteLine($"Failed to enumerate .NET runtime version information: {ex}"); + } + } + } + + private void OnRuntimeInformationStartEvent(RuntimeInformationTraceData data) + { + _loggedPaths.Add(data.RuntimeDllPath); + } + } + + public static bool Running + { + get { return _loggerInstance != null; } + } + + public static void Start() + { + if (App.CommandLineArgs.DisableDotNetVersionLogging) + { + return; + } + + Stop(); + + _loggerInstance = new VersionLogger(); + _loggerInstance.Start(); + } + + public static void StartRundown() + { + if (_loggerInstance != null) + { + _loggerInstance.StartRundown(); + } + } + + public static void Stop() + { + if (App.CommandLineArgs.DisableDotNetVersionLogging) + { + return; + } + + if (_loggerInstance != null) + { + _loggerInstance.Stop(); + _loggerInstance.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/PerfView/PerfViewLogger.cs b/src/PerfView/PerfViewLogger.cs index 2adb28fe4..d3ed474f0 100644 --- a/src/PerfView/PerfViewLogger.cs +++ b/src/PerfView/PerfViewLogger.cs @@ -99,6 +99,8 @@ public void EventStopTrigger(DateTime eventTime, int processID, int threadID, st { WriteEvent(25, eventTime, processID, threadID, processName, eventName, durationMSec); } [Event(26)] public void StopTriggerDebugMessage(DateTime eventTime, string message) { WriteEvent(26, eventTime, message); } + [Event(27)] + public void RuntimeVersion(string path, string version) { WriteEvent(27, path, version); } public class Tasks { public const EventTask Tracing = (EventTask)1; diff --git a/src/PerfViewCollect/PerfViewCollect.csproj b/src/PerfViewCollect/PerfViewCollect.csproj index 7642acab3..d925363aa 100644 --- a/src/PerfViewCollect/PerfViewCollect.csproj +++ b/src/PerfViewCollect/PerfViewCollect.csproj @@ -44,6 +44,7 @@ +