diff --git a/src/PerfView/EventViewer/EventWindow.xaml.cs b/src/PerfView/EventViewer/EventWindow.xaml.cs index d75ff1a86..3eff8bbb1 100644 --- a/src/PerfView/EventViewer/EventWindow.xaml.cs +++ b/src/PerfView/EventViewer/EventWindow.xaml.cs @@ -1,6 +1,9 @@ -using EventSources; +using Diagnostics.Tracing.StackSources; +using EventSources; using Microsoft.Diagnostics.Symbols; +using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Etlx; +using Microsoft.Diagnostics.Tracing.Stacks; using Microsoft.Diagnostics.Tracing.TraceUtilities.FilterQueryExpression; using Microsoft.Diagnostics.Utilities; using Microsoft.IdentityModel.Tokens; @@ -459,42 +462,39 @@ private void OpenStacks(string stackSourceName) var startTimeRelativeMSec = m_source.StartTimeRelativeMSec; var endTimeRelativeMSec = m_source.EndTimeRelativeMSec; var selectedCells = Grid.SelectedCells; - if (selectedCells.Count == 1 || selectedCells.Count == 2) + if (selectedCells.Count == 2) { string start = GetCellStringValue(selectedCells[0]); double parsedStart; if (!double.TryParse(start, out parsedStart)) { - if (selectedCells.Count != 1) - { StatusBar.LogError("Could not parse " + start + " as a number."); + OpenSelectedStacks(stackSourceName, selectedCells); return; - } - StatusBar.Log("Assuming total current time range"); } - else + + startTimeRelativeMSec = parsedStart; + string end = this.GetCellStringValue(selectedCells[1]); + if (!double.TryParse(end, out endTimeRelativeMSec)) { - startTimeRelativeMSec = parsedStart; - endTimeRelativeMSec = parsedStart; - if (selectedCells.Count == 2) - { - string end = GetCellStringValue(selectedCells[1]); - if (!double.TryParse(end, out endTimeRelativeMSec)) - { - StatusBar.LogError("Could not parse " + end + " as a number."); - return; - } - } + this.StatusBar.LogError("Could not parse " + end + " as a number."); + this.OpenSelectedStacks(stackSourceName, selectedCells); + return; + } - // Make sure that start < end - if (endTimeRelativeMSec < startTimeRelativeMSec) - { - var tmp = startTimeRelativeMSec; - startTimeRelativeMSec = endTimeRelativeMSec; - endTimeRelativeMSec = tmp; - } + // Make sure that start < end + if (endTimeRelativeMSec < startTimeRelativeMSec) + { + var tmp = startTimeRelativeMSec; + startTimeRelativeMSec = endTimeRelativeMSec; + endTimeRelativeMSec = tmp; } } + else if (selectedCells.Count != 2) + { + OpenSelectedStacks(stackSourceName, selectedCells); + return; + } // TODO FIX NOW: this should call a routine that does the opening of the stack view // (m_lookedUpCachedSymbolsForETLData should not be needed ...) @@ -557,6 +557,125 @@ private void OpenStacks(string stackSourceName) }); } } + + private void OpenSelectedStacks(string stackSourceName, IList selectedCells) + { + if (selectedCells == null || selectedCells.Count == 0) + { + StatusBar.LogError("No events selected."); + return; + } + + StatusBar.StartWork("Reading " + DataSource.Name, delegate () + { + PerfViewStackSource dataSource = null; + var dataFile = DataSource.DataFile; + if (dataFile != null) + { + if (stackSourceName == null) + { + stackSourceName = dataFile.DefaultStackSourceName; + } + + dataSource = dataFile.GetStackSource(stackSourceName); + } + + if (dataSource == null) + { + throw new ApplicationException("Could not find stack source " + stackSourceName); + } + + // Collect unique event records (a row may have multiple selected cells). + var uniqueRecords = selectedCells + .Select(c => c.Item as EventRecord) + .Where(r => r != null) + .Distinct() + .ToList(); + + if (uniqueRecords.Count == 0) + { + StatusBar.EndWork(delegate () + { + StatusBar.LogError("No event records found in selection."); + }); + return; + } + + StackSource aggregateSource; + + // For ETW sources, filter by exact EventIndex so concurrent events on other + // threads at the same timestamp are excluded. + var etwRecords = uniqueRecords.OfType().ToList(); + var startTimeRelativeMSec = etwRecords.Min(r => r.TimeStampRelatveMSec); + var endTimeRelativeMSec = etwRecords.Max(r => r.TimeStampRelatveMSec); + if (etwRecords.Count == uniqueRecords.Count) + { + var selectedIndices = new HashSet(etwRecords.Select(r => r.Index)); + aggregateSource = dataSource.GetStackSource( + StatusBar.LogWriter, + startTimeRelativeMSec - .001, + endTimeRelativeMSec + .001, + data => selectedIndices.Contains(data.EventIndex)); + } + else + { + // Fall back to per-event time windows for non-ETW sources. + var sources = new List(); + foreach (var record in uniqueRecords) + { + sources.Add(dataSource.GetStackSource( + StatusBar.LogWriter, + record.TimeStampRelatveMSec - .001, + record.TimeStampRelatveMSec + .001)); + } + aggregateSource = InternStackSource.Merge(sources); + } + + if (aggregateSource == null) + { + StatusBar.EndWork(delegate () + { + StatusBar.LogError("Could not open stacks for selected events."); + }); + return; + } + + if (!m_lookedUpCachedSymbolsForETLData) + { + m_lookedUpCachedSymbolsForETLData = true; + StatusBar.Log("Quick Looking up symbols from PDB cache."); + var etlDataFile = dataFile as ETLPerfViewData; + if (etlDataFile != null) + { + var traceLog = etlDataFile.GetTraceLog(StatusBar.LogWriter); + using (var reader = etlDataFile.GetSymbolReader(StatusBar.LogWriter, + SymbolReaderOptions.CacheOnly | SymbolReaderOptions.NoNGenSymbolCreation)) + { + var moduleFiles = ETLPerfViewData.GetInterestingModuleFiles(etlDataFile, 5.0, StatusBar.LogWriter, null); + foreach (var moduleFile in moduleFiles) + { + traceLog.CodeAddresses.LookupSymbolsForModule(reader, moduleFile); + } + } + } + StatusBar.Log("Quick Done looking up symbols from PDB cache."); + } + + StatusBar.EndWork(delegate () + { + App.CommandProcessor.NoExitOnElevate = true; + + var stackWindow = new PerfView.StackWindow(this, dataSource); + stackWindow.StatusBar.Log("Read " + DataSource.Name); + dataSource.ConfigureStackWindow(stackWindow); + stackWindow.StartTextBox.Text = startTimeRelativeMSec.ToString(); + stackWindow.EndTextBox.Text = endTimeRelativeMSec.ToString(); + stackWindow.Show(); + stackWindow.SetStackSource(aggregateSource); + }); + }); + } + private void DoProcessFilter(object sender, ExecutedRoutedEventArgs e) { var selectedCells = Grid.SelectedCells; diff --git a/src/PerfView/PerfViewData.cs b/src/PerfView/PerfViewData.cs index 9da2df702..bb1c2f7a6 100644 --- a/src/PerfView/PerfViewData.cs +++ b/src/PerfView/PerfViewData.cs @@ -4571,7 +4571,24 @@ public virtual StackSource GetStackSource(TextWriter log, double startRelativeMS return ret; } - // TODO not clear I want this method + public virtual StackSource GetStackSource(TextWriter log, double startRelativeMSec, double endRelativeMSec, Predicate predicate) + { + StackSource ret = DataFile.OpenStackSourceImpl(SourceName, log, startRelativeMSec, endRelativeMSec, predicate); + if (ret == null) + { + // Fall back to implementation without predicate + ret = DataFile.OpenStackSourceImpl(SourceName, log, startRelativeMSec, endRelativeMSec); + } + + if (ret == null) + { + throw new ApplicationException("Not a file type that supports the StackView."); + } + + return ret; + } + + // TODO not clear I want this method protected virtual StackSource OpenStackSource( string streamName, TextWriter log, double startRelativeMSec = 0, double endRelativeMSec = double.PositiveInfinity, Predicate predicate = null) { diff --git a/src/TraceEvent/Stacks/Stacks.cs b/src/TraceEvent/Stacks/Stacks.cs index ffc41df85..f9cb48e0a 100644 --- a/src/TraceEvent/Stacks/Stacks.cs +++ b/src/TraceEvent/Stacks/Stacks.cs @@ -737,6 +737,30 @@ public InternStackSource(StackSource source, StackSourceStacks sourceStacks) Interner.DoneInterning(); } + /// + /// Merge multiple stack sources into a single unified source by re-interning all frames by name. + /// Frames with the same name across sources (e.g. the same process frame) are automatically merged. + /// + public static InternStackSource Merge(IEnumerable sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + var ret = new InternStackSource(); + foreach (var source in sources) + { + if (source == null) + { + continue; + } + ret.ReadAllSamples(source, source, 1.0F); + } + ret.Interner.DoneInterning(); + return ret; + } + /// /// Create a new InternStackSource ///