From 3aa4ea9f111354be59b34a2169a0877220afe394 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:34:27 +0200 Subject: [PATCH 001/148] Fix broken links in PerfView user guide documentation (#2225) --- src/PerfView/SupportFiles/UsersGuide.htm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PerfView/SupportFiles/UsersGuide.htm b/src/PerfView/SupportFiles/UsersGuide.htm index f5b7c3f3e..fe33c7842 100644 --- a/src/PerfView/SupportFiles/UsersGuide.htm +++ b/src/PerfView/SupportFiles/UsersGuide.htm @@ -4121,7 +4121,7 @@

Advanced Options

if the application allocates aggressively, so many events will be fired so quickly that events will be lost even when the /BufferSizeMB qualifier is used to set the size very large (e.g. 500Meg). For these reasons it - is usually a better idea to use the .NET SampAlloc + is usually a better idea to use the .NET SampAlloc option instead if at all possible.

@@ -4143,7 +4143,7 @@

Advanced Options

reported is likely to be close to the true statistics.

The overhead of turning on .NET SampAlloc CheckBox is much less than the - .NET Alloc CheckBox. Typically the overhead is + .NET Alloc CheckBox. Typically the overhead is 10-20% (unlike 2X or more), and produces 200 Meg per minute of trace. This is a bit more expensive than turning on /threadTime however low enough that you can leave it on in production (especially if the application does not allocate heavily). @@ -5413,7 +5413,7 @@

Thread Time is not Elapsed Wall Clock Time

add up to more than elapsed wall clock time. This is easy to determine this is the case (because you will see more than one thread as children of the activity), and you can even see the overlap (by looking at the 'when' column of each of the children). Still it is something to - be aware of. See Understanding Thread Time and for more. + be aware of. See Understanding Thread Time and for more.

It is also possible that the thread time will be LESS than elapsed wall clock time. From ee7ef7605fc23fca8a2a7d674ea6d21cbb5c330b Mon Sep 17 00:00:00 2001 From: Bart Vries Date: Wed, 11 Jun 2025 14:48:06 +0200 Subject: [PATCH 002/148] Throw exception then TdhEnumerateProviders fails, which allows looking at the hr instead of causing an NRE which masks the exception. (#2177) --- src/TraceEvent/TraceEventSession.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TraceEvent/TraceEventSession.cs b/src/TraceEvent/TraceEventSession.cs index 728559c11..8752bfa17 100644 --- a/src/TraceEvent/TraceEventSession.cs +++ b/src/TraceEvent/TraceEventSession.cs @@ -1779,7 +1779,7 @@ internal static SortedDictionary ProviderNameToGuid } else { - Trace.WriteLine("TdhEnumerateProviders failed HR = " + hr); + throw new Exception("TdhEnumerateProviders failed HR = " + hr); } } } From 5cbea4fa83bbb7b20d449234840896478f0dc8ba Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:23:42 -0700 Subject: [PATCH 003/148] Add AutomationProperties.Name to Process Selection DataGrid for accessibility (#2239) * Initial plan for issue * Add AutomationProperties.Name to Process selection DataGrid for accessibility Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com> * Update name casing. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com> Co-authored-by: Brian Robbins --- src/PerfView/Dialogs/SelectProcess.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PerfView/Dialogs/SelectProcess.xaml b/src/PerfView/Dialogs/SelectProcess.xaml index 83c03b548..8798a5019 100644 --- a/src/PerfView/Dialogs/SelectProcess.xaml +++ b/src/PerfView/Dialogs/SelectProcess.xaml @@ -61,7 +61,8 @@ AutoGenerateColumns="False" MouseDoubleClick="OKClicked" IsReadOnly="True" - ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}"> + ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}" + AutomationProperties.Name="Process Selection Table"> ``` I gave up on updating the Azure packages as they cause PerfView to fail at runtime. Would require a bit more time. --- PerfView.sln | 10 +- src/Directory.Build.props | 2 +- src/Directory.Packages.props | 34 +- src/PerfView/ClrStats.cs | 48 +- src/PerfView/Dialogs/SelectProcess.xaml | 2 + src/PerfView/EventViewer/EventWindow.xaml | 2 + src/PerfView/GcStats.cs | 17 +- .../GuiUtilities/WebBrowser/WebBrowser.xaml | 4 +- .../WebBrowser/WebBrowser.xaml.cs | 17 +- src/PerfView/MainWindow.xaml | 2 +- src/PerfView/PerfView.csproj | 17 +- src/PerfView/PerfViewData.cs | 129 +++-- src/PerfView/StackViewer/PerfDataGrid.xaml | 2 + .../EventCounterVisualization.html | 6 +- .../SupportFiles/HtmlReportUsersGuide.htm | 14 +- .../SupportFiles/PerfViewWebVideos.htm | 18 +- src/PerfView/SupportFiles/UsersGuide.htm | 532 +++++++++--------- src/PerfView/Themes/DarkTheme.xaml | 43 +- src/PerfView/app.manifest | 10 + 19 files changed, 523 insertions(+), 386 deletions(-) diff --git a/PerfView.sln b/PerfView.sln index 033049177..e55ed9e79 100644 --- a/PerfView.sln +++ b/PerfView.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.32821.20 MinimumVisualStudioVersion = 15.0 @@ -38,6 +39,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution CONTRIBUTING.md = CONTRIBUTING.md src\Directory.Build.props = src\Directory.Build.props src\Directory.Build.targets = src\Directory.Build.targets + src\Directory.Packages.props = src\Directory.Packages.props src\PerfViewCollect\PerfViewCollect.csproj = src\PerfViewCollect\PerfViewCollect.csproj README.md = README.md EndProjectSection @@ -407,13 +409,13 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {DE35BED9-0E03-4DAC-A003-1ACBBF816973} = {1CAEF854-2923-45FA-ACB8-6523A7E45896} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F85A2A3-E0DF-4826-9BBA-4DFFA0F17150} EndGlobalSection GlobalSection(TestCaseManagementSettings) = postSolution CategoryFile = PerfView2.vsmdi EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {DE35BED9-0E03-4DAC-A003-1ACBBF816973} = {1CAEF854-2923-45FA-ACB8-6523A7E45896} - EndGlobalSection EndGlobal diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 929e2686f..7814d0993 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,7 +6,7 @@ - 8.0 + 11.0 strict diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 89b599aca..1a545da2b 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -19,27 +19,27 @@ - - - - - + + + + + - - - - - - - + + + + + + + - + - - - - + + + + diff --git a/src/PerfView/ClrStats.cs b/src/PerfView/ClrStats.cs index 574281211..19156a78a 100644 --- a/src/PerfView/ClrStats.cs +++ b/src/PerfView/ClrStats.cs @@ -16,20 +16,41 @@ public static void ToHtml(TextWriter writer, List perProc, string { if (!justBody) { - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine("{0}", Path.GetFileNameWithoutExtension(fileName)); - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine(""); + writer.WriteLine($$""" + + + {{Path.GetFileNameWithoutExtension(fileName)}} + + + + +

{{title}}

+ """); } - writer.WriteLine("

{0}

", title); + List sortedProcs = perProc; if (type == ReportType.JIT) { sortedProcs.Sort((TraceProcess p1, TraceProcess p2) => { return -p1.LoadedDotNetRuntime().JIT.Stats().TotalCpuTimeMSec.CompareTo(p2.LoadedDotNetRuntime().JIT.Stats().TotalCpuTimeMSec); }); } + else if (type == ReportType.GC) { sortedProcs.Sort((TraceProcess p1, TraceProcess p2) => { return -p1.LoadedDotNetRuntime().GC.Stats().MaxSizePeakMB.CompareTo(p2.LoadedDotNetRuntime().GC.Stats().MaxSizePeakMB); }); @@ -110,8 +131,15 @@ public static void ToHtml(TextWriter writer, List perProc, string writer.WriteLine("









"); if (!justBody) { - writer.WriteLine(""); - writer.WriteLine(""); + writer.WriteLine(""" + + + + """); } } diff --git a/src/PerfView/Dialogs/SelectProcess.xaml b/src/PerfView/Dialogs/SelectProcess.xaml index 8798a5019..f4af6771a 100644 --- a/src/PerfView/Dialogs/SelectProcess.xaml +++ b/src/PerfView/Dialogs/SelectProcess.xaml @@ -11,6 +11,8 @@ diff --git a/src/PerfView/EventViewer/EventWindow.xaml b/src/PerfView/EventViewer/EventWindow.xaml index 57ad60b74..600d963f8 100644 --- a/src/PerfView/EventViewer/EventWindow.xaml +++ b/src/PerfView/EventViewer/EventWindow.xaml @@ -16,6 +16,8 @@ diff --git a/src/PerfView/GcStats.cs b/src/PerfView/GcStats.cs index e0e8b099c..9b1c6fa72 100644 --- a/src/PerfView/GcStats.cs +++ b/src/PerfView/GcStats.cs @@ -20,11 +20,14 @@ internal static class GcStats public static void ToHtml(TextWriter writer, TraceProcess stats, TraceLoadedDotNetRuntime runtime, string fileName, bool doServerGCReport = false) { - writer.WriteLine("

GC Stats for Process {1,5}: {2}

", stats.ProcessID, stats.ProcessID, stats.Name); - writer.WriteLine(" +
  • + Version 1.8.28 2/4/16 +
    • - Version 1.8.28 2/4/16 -
        -
      • - Added support doing performance investigations with Linux Perf Events data. Basically if - collect data with the bash script https://raw.githubusercontent.com/dotnet/corefx-tools/master/src/performance/perfcollect/perfcollect - it will runt the Linux 'perf' tool that will collect CPU samples, convert them to a .data.txt file - (which is a textual representation of the data) and then ZIP it into a .trace.zip file PerfView - knows how to decode either the uncompressed .data.txt file or the zipped .trace.zip file and - display it as a stack view. Thus you can now do linux performance investigations with PerfView. -
      • -
      + Added support doing performance investigations with Linux Perf Events data. Basically if + collect data with the bash script https://raw.githubusercontent.com/dotnet/corefx-tools/master/src/performance/perfcollect/perfcollect + it will runt the Linux 'perf' tool that will collect CPU samples, convert them to a .data.txt file + (which is a textual representation of the data) and then ZIP it into a .trace.zip file PerfView + knows how to decode either the uncompressed .data.txt file or the zipped .trace.zip file and + display it as a stack view. Thus you can now do linux performance investigations with PerfView.
    • +
    +
  • +
  • + Version 1.8.25 2/2/16 +
    • - Version 1.8.25 2/2/16 -
        -
      • - Improvements in Start-Stop time. UNKNOWN_ASYNC displayed more often, some AWAIT time shown more often. -
      • -
      + Improvements in Start-Stop time. UNKNOWN_ASYNC displayed more often, some AWAIT time shown more often.
    • +
    +
  • +
  • + Version 1.8.24 1/27/16 +
    • - Version 1.8.24 1/27/16 -
        -
      • - When opening 'Drill Into' windows, the columns are not in the order of the parent window in the ByName view. - Fixed this. -
      • -
      + When opening 'Drill Into' windows, the columns are not in the order of the parent window in the ByName view. + Fixed this.
    • +
    +
  • +
  • + Version 1.8.23 1/26/16 +
    • - Version 1.8.23 1/26/16 -
        -
      • - Merging failed on Win7 and Win2k8 systems in PerfView Version 1.8. This means you could still analyze on - the machine where you collected, but symbols would fail to look up if you took the trace off the system. - Fixed by including an old version of KernelTraceControl.dll an used it on Win7 systems. -
      • -
      + Merging failed on Win7 and Win2k8 systems in PerfView Version 1.8. This means you could still analyze on + the machine where you collected, but symbols would fail to look up if you took the trace off the system. + Fixed by including an old version of KernelTraceControl.dll an used it on Win7 systems.
    • +
    +
  • +
  • + Version 1.8.22 1/23/16 +
    • - Version 1.8.22 1/23/16 -
        -
      • - Fixed ArgumentOutOfRange exceptions thrown in EventView for some events (strings with length prefixes) -
      • -
      • Don't crash if regular expressions are incorrect in Events view.
      • -
      + Fixed ArgumentOutOfRange exceptions thrown in EventView for some events (strings with length prefixes)
    • +
    • Don't crash if regular expressions are incorrect in Events view.
    • +
    +
  • +
  • + Version 1.8.21 1/18/16 +
    • - Version 1.8.21 1/18/16 -
        -
      • - Extended perfView.xml file format so that it can more easily consume 'ad hoc' creation of stacks. - It still accepts the 'interned' scheme where you give IDs to each frame and stack and use those - to create samples, but now you can specify the samples inline with the sample like this -
        +                    Extended perfView.xml file format so that it can more easily consume 'ad hoc' creation of stacks.
        +                    It still accepts the 'interned' scheme where you give IDs to each frame and stack and use those
        +                    to create samples, but now you can specify the samples inline with the sample like this
        +                    
         <StackWindow>
             <StackSource>
                 <Samples>
        @@ -9238,230 +9246,234 @@ 

        </StackSource> </StackWindow>

        - While this format is inefficient (you repeat many strings in many stacks), it is sometimes - convenient, and it is easy enough to support. There are more details which I will blog about in - the near future. -
      • -
      + While this format is inefficient (you repeat many strings in many stacks), it is sometimes + convenient, and it is easy enough to support. There are more details which I will blog about in + the near future.
    • +
    +
  • +
  • + Version 1.8.20 1/13/16 +
    • - Version 1.8.20 1/13/16 -
        -
      • - Improved the robustness of the UserCommand 'Listen' command in the face of bad events. -
      • -
      + Improved the robustness of the UserCommand 'Listen' command in the face of bad events.
    • +
    +
  • +
  • + Version 1.8.19 1/7/16 +
    • - Version 1.8.19 1/7/16 -
        -
      • - Significantly improved the Thread Time with Start-Stop Activities. The goal here is - that this view replaces the ASP.NET and Service Request view, and we are probably most of - the way there now. I need to validate this more and then probably obsolete the other views. -
      • -
      + Significantly improved the Thread Time with Start-Stop Activities. The goal here is + that this view replaces the ASP.NET and Service Request view, and we are probably most of + the way there now. I need to validate this more and then probably obsolete the other views.
    • +
    +
  • +
  • + Version 1.8.15 12/4/15 +
    • - Version 1.8.15 12/4/15 -
        -
      • - Fixed a fairly serious bug associated with the Events Viewer where you don't see some CLR events - (They appear in the left pane, but you never see them in the right pane even though there are - instances of them in the file). Note that version 1.8.0 does not have this bug, it was introduced - relatively recently. -
      • -
      • - Added ActivityInfo and StartStopActivity fields to Events View. ActivityInfo will show you the - creation and start time (and the raw ID) of the System.Threading.Tasks.Task that logged the event. - StartStopActivity shows you the name of the start-stop activity that - is logged the event. -
      • -
      + Fixed a fairly serious bug associated with the Events Viewer where you don't see some CLR events + (They appear in the left pane, but you never see them in the right pane even though there are + instances of them in the file). Note that version 1.8.0 does not have this bug, it was introduced + relatively recently.
    • - Version 1.8.15 12/4/15 -
        -
      • - Fixed a fairly serious bug associated with the Events Viewer where you don't see some CLR events - (They appear in the left pane, but you never see them in the right pane even though there are - instances of them in the file). Note that version 1.8.0 does not have this bug, it was introduced - relatively recently. -
      • -
      • - Added ActivityInfo and StartStopActivity fields to Events View. ActivityInfo will show you the - creation and start time (and the raw ID) of the System.Threading.Tasks.Task that logged the event. - StartStopActivity shows you the name of the start-stop activity that - is logged the event. -
      • -
      + Added ActivityInfo and StartStopActivity fields to Events View. ActivityInfo will show you the + creation and start time (and the raw ID) of the System.Threading.Tasks.Task that logged the event. + StartStopActivity shows you the name of the start-stop activity that + is logged the event.
    • +
    +
  • +
  • + Version 1.8.15 12/4/15 +
    • - Version 1.8.11 11/16/15 -
        -
      • - Fix excessive warnings when converting ETL files. Might also fix some StartStop Activity issues. -
      • -
      + Fixed a fairly serious bug associated with the Events Viewer where you don't see some CLR events + (They appear in the left pane, but you never see them in the right pane even though there are + instances of them in the file). Note that version 1.8.0 does not have this bug, it was introduced + relatively recently.
    • - Version 1.8.10 11/12/15 -
        -
      • - Significant improvement in how activity tracking works. Hopefully the stacks associated with 'with Tasks' views - will be better. -
      • -
      • - Added JIT Inlining feature that enables viewing all successful and failed inlining attempts, including the - JIT-supplied reason for why inlining wasn't performed in the failure cases. -
      • -
      • - Added finalization feature that tracks finalized objects and provides a table of each type with a finalized object - and the associated number of times an object of that type was finalized. -
      • -
      + Added ActivityInfo and StartStopActivity fields to Events View. ActivityInfo will show you the + creation and start time (and the raw ID) of the System.Threading.Tasks.Task that logged the event. + StartStopActivity shows you the name of the start-stop activity that + is logged the event.
    • +
    +
  • +
  • + Version 1.8.11 11/16/15 +
    • - Version 1.8.9 11/1/15 -
        -
      • - There is a bug in RC candidates of V4.6.1 where NGEN createPdb only works if the path of the NGEN image - is in the Native Image Cache (NIC), but V4.6.1 uses hard links for NGEN images that come from the install itself. - The result is that you don't get symbols for mscorlib, system, and system.core. This adds a work-around - for this (normally all paths to the NIC path before calling NGEN CreatePdb), until the runtime is fixed. -
      • -
      + Fix excessive warnings when converting ETL files. Might also fix some StartStop Activity issues.
    • +
    +
  • +
  • + Version 1.8.10 11/12/15 +
    • - Version 1.8.8 10/31/15 -
        -
      • - Added support for .NET V4.6.2 convention for NGEN PDB line numbers. This means that if data is collected on - a V4.6.2 then the lack of access IL PDBS are not available at data collection time is not longer an - impediment to getting line number information (that is access to the corresponding IL pdb with line number - information is no longer needed to create an NGEN pdb that has line number information). -
      • -
      + Significant improvement in how activity tracking works. Hopefully the stacks associated with 'with Tasks' views + will be better.
    • - Version 1.8.7 10/22/15 -
        -
      • - Integrated changes that allow DyanamicTraceEventParser to do everything that RegisteredTraceEventParser can do. - Removed the calls to RegisteredTraceEventParser. This could break things but should not. So far things look - OK. -
      • -
      + Added JIT Inlining feature that enables viewing all successful and failed inlining attempts, including the + JIT-supplied reason for why inlining wasn't performed in the failure cases.
    • - Version 1.8.6 10/12/15 -
        -
      • - Integrated Lee's update of CLRMD that should make PerfView able to extract heap dumps from debugger dumps of - .NET Native processes. -
      • -
      • Added the DotNet (Telemetry) event ETW provider by default.
      • -
      + Added finalization feature that tracks finalized objects and provides a table of each type with a finalized object + and the associated number of times an object of that type was finalized.
    • +
    +
  • +
  • + Version 1.8.9 11/1/15 +
    • - Version 1.8.5 10/6/15 -
        -
      • - Made 'Any Stacks (with StartStop Activities)' and 'Any StartStopTree' public. -
      • -
      + There is a bug in RC candidates of V4.6.1 where NGEN createPdb only works if the path of the NGEN image + is in the Native Image Cache (NIC), but V4.6.1 uses hard links for NGEN images that come from the install itself. + The result is that you don't get symbols for mscorlib, system, and system.core. This adds a work-around + for this (normally all paths to the NIC path before calling NGEN CreatePdb), until the runtime is fixed.
    • +
    +
  • +
  • + Version 1.8.8 10/31/15 +
    • - Version 1.8.3 9/23/15 -
        -
      • - Turned off System.Threading.Tasks.Task events that are verbose and only needed for debugging. This was - useful before so that any traces I get have detailed information for debugging, but are now impacting - the cost of using PerfView in production when Tasks are used heavily. -
      • -
      + Added support for .NET V4.6.2 convention for NGEN PDB line numbers. This means that if data is collected on + a V4.6.2 then the lack of access IL PDBS are not available at data collection time is not longer an + impediment to getting line number information (that is access to the corresponding IL pdb with line number + information is no longer needed to create an NGEN pdb that has line number information).
    • +
    +
  • +
  • + Version 1.8.7 10/22/15 +
    • - Version 1.8.2 9/13/15 -
        -
      • /InMemoryCircularBuffer option was broken (Would throw a file not found exception in SetFileName). Fixed this.
      • -
      + Integrated changes that allow DyanamicTraceEventParser to do everything that RegisteredTraceEventParser can do. + Removed the calls to RegisteredTraceEventParser. This could break things but should not. So far things look + OK.
    • +
    +
  • +
  • + Version 1.8.6 10/12/15 +
    • - Version 1.8.1 9/3/15 -
        -
      • - Fixed issue where Debug versions were asserting that two stacks were attached to the same event - because kernel and user mode stacks were not being stitched together properly (mostly in rare cases - where thread-starts were happening) -
      • -
      + Integrated Lee's update of CLRMD that should make PerfView able to extract heap dumps from debugger dumps of + .NET Native processes.
    • - +
    • Added the DotNet (Telemetry) event ETW provider by default.
    • +
    +
  • +
  • + Version 1.8.5 10/6/15 +
    • - Version 1.8.0 8/30/15 -
        -
      • Release to Web.
      • -
      + Made 'Any Stacks (with StartStop Activities)' and 'Any StartStopTree' public.
    • +
    +
  • +
  • + Version 1.8.3 9/23/15 +
    • - Version 1.7.31 8/19/15 -
        -
      • - Update code that does merging so it works properly on Win10. It does not have an effect if you look - at the events with PerfView, but on Win10 until this change, data collected with PerfView would not - parse EventSource events properly in WPA. -
      • -
      + Turned off System.Threading.Tasks.Task events that are verbose and only needed for debugging. This was + useful before so that any traces I get have detailed information for debugging, but are now impacting + the cost of using PerfView in production when Tasks are used heavily.
    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    -

    -   -

    - +
  • +
  • + Version 1.8.2 9/13/15 +
      +
    • /InMemoryCircularBuffer option was broken (Would throw a file not found exception in SetFileName). Fixed this.
    • +
    +
  • +
  • + Version 1.8.1 9/3/15 +
      +
    • + Fixed issue where Debug versions were asserting that two stacks were attached to the same event + because kernel and user mode stacks were not being stitched together properly (mostly in rare cases + where thread-starts were happening) +
    • +
    +
  • +
  • + Version 1.8.0 8/30/15 +
      +
    • Release to Web.
    • +
    +
  • +
  • + Version 1.7.31 8/19/15 +
      +
    • + Update code that does merging so it works properly on Win10. It does not have an effect if you look + at the events with PerfView, but on Win10 until this change, data collected with PerfView would not + parse EventSource events properly in WPA. +
    • +
    +
  • + +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    +

    +   +

    + + diff --git a/src/PerfView/Themes/DarkTheme.xaml b/src/PerfView/Themes/DarkTheme.xaml index e7ca6a37c..05746196c 100644 --- a/src/PerfView/Themes/DarkTheme.xaml +++ b/src/PerfView/Themes/DarkTheme.xaml @@ -63,6 +63,10 @@ SOFTWARE. + + + + @@ -2624,17 +2628,23 @@ SOFTWARE. - + + + - - + + - - - + + + @@ -2651,18 +2661,23 @@ SOFTWARE. - - + + + - - + + - - - + + + @@ -4069,7 +4084,7 @@ SOFTWARE. + + + + + PerMonitorV2 + + + true/PM + + From 45dfab9d5a1bc4cd9114790a8618d913d7329441 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 Aug 2025 12:13:20 -0700 Subject: [PATCH 026/148] Implement configurable symbol server authentication with /SymbolsAuth command line argument for PerfView and HeapDump (#2278) Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com> Co-authored-by: Brian Robbins --- src/HeapDump/GCHeapDumper.cs | 20 ++++--- src/HeapDump/HeapDump.csproj | 5 +- src/HeapDump/Program.cs | 56 ++++++++++++++++--- src/PerfView/Authentication.cs | 41 ++++++++++++-- src/PerfView/CommandLineArgs.cs | 3 + src/PerfView/CommandProcessor.cs | 5 ++ src/PerfView/PerfView.csproj | 10 ++++ src/PerfView/memory/HeapSnapshot.cs | 18 +++++- src/PerfViewCollect/PerfViewCollect.csproj | 1 + .../SymbolsAuthenticationUtilities.cs | 56 +++++++++++++++++++ src/Utilities/Utilities.csproj | 4 ++ 11 files changed, 195 insertions(+), 24 deletions(-) create mode 100644 src/Utilities/SymbolsAuthenticationUtilities.cs diff --git a/src/HeapDump/GCHeapDumper.cs b/src/HeapDump/GCHeapDumper.cs index 4be63bc72..ad374b8ba 100644 --- a/src/HeapDump/GCHeapDumper.cs +++ b/src/HeapDump/GCHeapDumper.cs @@ -21,6 +21,8 @@ using Microsoft.Diagnostics.Utilities; using Microsoft.Diagnostics.HeapDump; using Azure.Core; +using Azure.Identity; + #if CROSS_GENERATION_LIVENESS @@ -38,10 +40,10 @@ public class GCHeapDumper /// to dump a heap. /// /// - public GCHeapDumper(TextWriter log, TokenCredential symbolServerAuthCredential = null) + public GCHeapDumper(TextWriter log) { m_origLog = log; - m_symbolServerAuthCredential = symbolServerAuthCredential; + SymbolsAuthTokenCredential = new InteractiveBrowserCredential(); m_copyOfLog = new StringWriter(); m_log = new TeeTextWriter(m_copyOfLog, m_origLog); @@ -284,11 +286,11 @@ private DataTarget InitializeClrRuntime(string processDumpFile, int processID, o { try { - dataTarget = DataTarget.CreateSnapshotAndAttach(processID, m_symbolServerAuthCredential); + dataTarget = DataTarget.CreateSnapshotAndAttach(processID, SymbolsAuthTokenCredential); } catch { - dataTarget = DataTarget.AttachToProcess(processID, Freeze, m_symbolServerAuthCredential); + dataTarget = DataTarget.AttachToProcess(processID, Freeze, SymbolsAuthTokenCredential); } } else @@ -298,7 +300,7 @@ private DataTarget InitializeClrRuntime(string processDumpFile, int processID, o UseOSMemoryFeatures = false // disable AWE }; - dataTarget = DataTarget.LoadDump(processDumpFile, cacheOptions, m_symbolServerAuthCredential); + dataTarget = DataTarget.LoadDump(processDumpFile, cacheOptions, SymbolsAuthTokenCredential); } if (dataTarget.DataReader.PointerSize != IntPtr.Size) @@ -391,6 +393,12 @@ private DataTarget InitializeClrRuntime(string processDumpFile, int processID, o /// public ulong PromotedBytesThreshold; + + /// + /// The token credential to use for symbol server authentication. + /// + public TokenCredential SymbolsAuthTokenCredential; + /// /// Force a .NET GC on a particular process. /// @@ -1579,8 +1587,6 @@ private NodeTypeIndex GetTypeIndexForName(string typeName, string moduleName, in private StringWriter m_copyOfLog; // We keep a copy of all logged messages here to append to output file. private Stopwatch m_sw; // We keep track of how long it takes. - private TokenCredential m_symbolServerAuthCredential; - private GCHeapDump m_gcHeapDump; // The image of what we are putting in the file private NodeIndex m_JSRoot = NodeIndex.Invalid; // The root of the JS heap private NodeIndex m_dotNetRoot = NodeIndex.Invalid; // The root of the .NET heap diff --git a/src/HeapDump/HeapDump.csproj b/src/HeapDump/HeapDump.csproj index 058dc7f75..d10211bdf 100644 --- a/src/HeapDump/HeapDump.csproj +++ b/src/HeapDump/HeapDump.csproj @@ -68,7 +68,10 @@ Utilities\StringUtilities.cs - + + Utilities\SymbolsAuthenticationUtilities.cs + + diff --git a/src/HeapDump/Program.cs b/src/HeapDump/Program.cs index 441db9468..d8501f79c 100644 --- a/src/HeapDump/Program.cs +++ b/src/HeapDump/Program.cs @@ -1,7 +1,7 @@ -using Azure.Core; -using Azure.Identity; -using Microsoft.Diagnostics.Runtime; +using Microsoft.Diagnostics.Runtime; +using Microsoft.Diagnostics.Utilities; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -60,11 +60,7 @@ private static int MainWorker(string[] args) string inputSpec = null; int minSecForTrigger = -1; - ChainedTokenCredential symbolsTokenCredential = new ChainedTokenCredential( - new VisualStudioCredential(), - new InteractiveBrowserCredential()); - - var dumper = new GCHeapDumper(Console.Out, symbolsTokenCredential); + var dumper = new GCHeapDumper(Console.Out); for (int curArgIdx = 0; curArgIdx < args.Length; curArgIdx++) { @@ -174,6 +170,22 @@ private static int MainWorker(string[] args) } Console.WriteLine("Generation To Trigger: " + dumper.GenerationToTrigger); } + else if (arg.StartsWith("/SymbolsAuth:", StringComparison.OrdinalIgnoreCase)) + { + string authTypesStr = arg.Substring(13); + if (TryParseSymbolsAuthenticationTypes(authTypesStr, out SymbolsAuthenticationType parsedAuthTypes)) + { + dumper.SymbolsAuthTokenCredential = SymbolsAuthenticationUtilities.CreateTokenCredential(parsedAuthTypes); + Console.WriteLine("Set symbols authentication credentials to {0}", authTypesStr); + } + else + { + Console.WriteLine("Invalid value for /SymbolsAuth: {0}", authTypesStr); + var validValues = string.Join(", ", Enum.GetNames(typeof(SymbolsAuthenticationType))); + Console.WriteLine("Valid values are: {0} (can be combined with commas)", validValues); + goto Usage; + } + } else { Console.WriteLine("Unknown qualifier: {0}", arg); @@ -333,6 +345,34 @@ public static int GetProcessID(string processNameOrID) return -1; } + private static bool TryParseSymbolsAuthenticationTypes(string authTypesStr, out SymbolsAuthenticationType result) + { + result = (SymbolsAuthenticationType)0; + + if (string.IsNullOrWhiteSpace(authTypesStr)) + { + return false; + } + + var parts = authTypesStr.Split(','); + foreach (var part in parts) + { + var trimmedPart = part.Trim(); + if (Enum.TryParse(trimmedPart, true, out var parsedType)) + { + result |= parsedType; + } + else + { + return false; + } + } + + return result != (SymbolsAuthenticationType)0; + } + + + private static int PointerSizeForProcess(int processID) { if (!Environment.Is64BitOperatingSystem) diff --git a/src/PerfView/Authentication.cs b/src/PerfView/Authentication.cs index d17630af6..6778267e8 100644 --- a/src/PerfView/Authentication.cs +++ b/src/PerfView/Authentication.cs @@ -1,12 +1,15 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Input; using System.Windows.Interop; +using Azure.Core; using Azure.Identity; using Microsoft.Diagnostics.Symbols.Authentication; +using Microsoft.Diagnostics.Utilities; using Utilities; namespace PerfView @@ -256,7 +259,7 @@ public static void Configure(this SymbolReaderAuthenticationHandler handler, Aut /// This instance for fluent chaining. public static SymbolReaderAuthenticationHandler AddSymwebAuthentication(this SymbolReaderAuthenticationHandler httpHandler, TextWriter log, bool silent = false) { - return httpHandler.AddHandler(new SymwebHandler(log, CreateTokenCredential())); + return httpHandler.AddHandler(new SymwebHandler(log, CreateTokenCredential(App.CommandLineArgs.SymbolsAuth))); } /// @@ -279,7 +282,7 @@ public static SymbolReaderAuthenticationHandler AddGitCredentialManagerAuthentic /// This instance for fluent chaining. public static SymbolReaderAuthenticationHandler AddAzureDevOpsAuthentication(this SymbolReaderAuthenticationHandler httpHandler, TextWriter log, bool silent = false) { - return httpHandler.AddHandler(new AzureDevOpsHandler(log, CreateTokenCredential())); + return httpHandler.AddHandler(new AzureDevOpsHandler(log, CreateTokenCredential(App.CommandLineArgs.SymbolsAuth))); } /// @@ -295,11 +298,37 @@ public static SymbolReaderAuthenticationHandler AddGitHubDeviceCodeAuthenticatio public static SymbolReaderAuthenticationHandler AddBasicHttpAuthentication(this SymbolReaderAuthenticationHandler httpHandler, TextWriter log, Window mainWindow) => httpHandler.AddHandler(new BasicHttpAuthHandler(log)); - private static ChainedTokenCredential CreateTokenCredential() + private static ChainedTokenCredential CreateTokenCredential(SymbolsAuthenticationType authTypes) { - return new ChainedTokenCredential( - new VisualStudioCredential(), - new InteractiveBrowserCredential()); + var credentials = new List(); + + if (authTypes.HasFlag(SymbolsAuthenticationType.Environment)) + { + credentials.Add(new EnvironmentCredential()); + } + + if (authTypes.HasFlag(SymbolsAuthenticationType.AzureCli)) + { + credentials.Add(new AzureCliCredential()); + } + + if (authTypes.HasFlag(SymbolsAuthenticationType.VisualStudio)) + { + credentials.Add(new VisualStudioCredential()); + } + + if (authTypes.HasFlag(SymbolsAuthenticationType.Interactive)) + { + credentials.Add(new InteractiveBrowserCredential()); + } + + // If no credentials are specified, default to Interactive + if (credentials.Count == 0) + { + credentials.Add(new InteractiveBrowserCredential()); + } + + return new ChainedTokenCredential(credentials.ToArray()); } /// diff --git a/src/PerfView/CommandLineArgs.cs b/src/PerfView/CommandLineArgs.cs index 589fe1b70..de11a7765 100644 --- a/src/PerfView/CommandLineArgs.cs +++ b/src/PerfView/CommandLineArgs.cs @@ -1,6 +1,7 @@ using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Parsers; using Microsoft.Diagnostics.Tracing.Session; +using Microsoft.Diagnostics.Utilities; using System; using System.Collections.Generic; using System.Globalization; @@ -65,6 +66,7 @@ public static string GetHelpString(int maxLineWidth) // options common to multiple commands public string DataFile; // This is the name of the ETL file (not the ZIP file) public string LogFile; + public SymbolsAuthenticationType SymbolsAuth = SymbolsAuthenticationType.Interactive; // Specifies authentication types for symbol servers // Memory options public string ProcessDumpFile; // if taking a snapshot from a dump, this is the dump file (dataFile is the output file) @@ -277,6 +279,7 @@ private void SetupCommandLine(CommandLineParser parser) parser.NoDashOnParameterSets = true; parser.DefineOptionalQualifier("LogFile", ref LogFile, "Send messages to this file instead launching the GUI. Intended for batch scripts and other automation."); + parser.DefineOptionalQualifier("SymbolsAuth", ref SymbolsAuth, "Specifies authentication types for symbol servers. Values: Environment, AzureCli, VisualStudio, Interactive. Can be combined with +. Default is Interactive only."); // These apply to start, collect and run parser.DefineOptionalQualifier("BufferSize", ref BufferSizeMB, "The size the buffers (in MB) the OS should use to store events waiting to be written to disk."); // TODO remove eventually. diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index a21ada84b..db3fde90b 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -2997,6 +2997,11 @@ public static string ParsedArgsAsString(string command, CommandLineArgs parsedAr cmdLineArgs += " /LogFile:" + Command.Quote(parsedArgs.LogFile); } + if (parsedArgs.SymbolsAuth != SymbolsAuthenticationType.Interactive) + { + cmdLineArgs += " /SymbolsAuth:" + parsedArgs.SymbolsAuth.ToString().Replace(" ", ""); + } + if (parsedArgs.NoRundown) { cmdLineArgs += " /NoRundown"; diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index 1de1be816..21f2f431c 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -174,6 +174,9 @@ Utilities\StringUtilities.cs + + Utilities\SymbolsAuthenticationUtilities.cs + Utilities\XmlUtilities.cs @@ -304,6 +307,13 @@ HeapDump\System.Collections.Immutable.dll False + + Non-Resx + false + .\HeapDump\System.Runtime.CompilerServices.Unsafe.dll + HeapDump\System.Runtime.CompilerServices.Unsafe.dll + False + Non-Resx false diff --git a/src/PerfView/memory/HeapSnapshot.cs b/src/PerfView/memory/HeapSnapshot.cs index 47e6a772e..c7d8f09b3 100644 --- a/src/PerfView/memory/HeapSnapshot.cs +++ b/src/PerfView/memory/HeapSnapshot.cs @@ -75,7 +75,14 @@ internal static void ForceGC(int processID, TextWriter log = null) options.AddOutputStream(log); } - var commandLine = string.Format("\"{0}\" /ForceGC {1}", heapDumpExe, processID.ToString()); + // Add SymbolsAuth argument if specified + var symbolsAuthArg = ""; + if (App.CommandLineArgs.SymbolsAuth != SymbolsAuthenticationType.Interactive) + { + symbolsAuthArg = $" /SymbolsAuth:{App.CommandLineArgs.SymbolsAuth.ToString().Replace(" ", "")}"; + } + + var commandLine = string.Format("\"{0}\"{1} /ForceGC {2}", heapDumpExe, symbolsAuthArg, processID.ToString()); log.WriteLine("Exec: {0}", commandLine); var cmd = Command.Run(commandLine, options); if (cmd.ExitCode != 0) @@ -168,7 +175,14 @@ private static void DumpGCHeap(string qualifiers, string inputArg, string output options.AddEnvironmentVariable("_NT_SYMBOL_PATH", App.SymbolPath); log.WriteLine("set _NT_SYMBOL_PATH={0}", App.SymbolPath); - var commandLine = string.Format("\"{0}\" {1} \"{2}\" \"{3}\"", heapDumpExe, qualifiers, inputArg, outputFile); + // Add SymbolsAuth argument if specified + var symbolsAuthArg = ""; + if (App.CommandLineArgs.SymbolsAuth != SymbolsAuthenticationType.Interactive) + { + symbolsAuthArg = $" /SymbolsAuth:{App.CommandLineArgs.SymbolsAuth.ToString().Replace(" ", "")}"; + } + + var commandLine = string.Format("\"{0}\"{1} {2} \"{3}\" \"{4}\"", heapDumpExe, symbolsAuthArg, qualifiers, inputArg, outputFile); log.WriteLine("Exec: {0}", commandLine); PerfViewLogger.Log.TriggerHeapSnapshot(outputFile, inputArg, qualifiers); var cmd = Command.Run(commandLine, options); diff --git a/src/PerfViewCollect/PerfViewCollect.csproj b/src/PerfViewCollect/PerfViewCollect.csproj index f9995ab26..3a33ba8a2 100644 --- a/src/PerfViewCollect/PerfViewCollect.csproj +++ b/src/PerfViewCollect/PerfViewCollect.csproj @@ -34,6 +34,7 @@ + diff --git a/src/Utilities/SymbolsAuthenticationUtilities.cs b/src/Utilities/SymbolsAuthenticationUtilities.cs new file mode 100644 index 000000000..abf466d20 --- /dev/null +++ b/src/Utilities/SymbolsAuthenticationUtilities.cs @@ -0,0 +1,56 @@ +#if !PERFVIEW_COLLECT +using Azure.Core; +using Azure.Identity; +#endif +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.Utilities +{ + [Flags] + public enum SymbolsAuthenticationType + { + Environment = 1, + AzureCli = 2, + VisualStudio = 4, + Interactive = 8 + } + +#if !PERFVIEW_COLLECT + public static class SymbolsAuthenticationUtilities + { + internal static ChainedTokenCredential CreateTokenCredential(SymbolsAuthenticationType authTypes) + { + var credentials = new List(); + + if (authTypes.HasFlag(SymbolsAuthenticationType.Environment)) + { + credentials.Add(new EnvironmentCredential()); + } + + if (authTypes.HasFlag(SymbolsAuthenticationType.AzureCli)) + { + credentials.Add(new AzureCliCredential()); + } + + if (authTypes.HasFlag(SymbolsAuthenticationType.VisualStudio)) + { + credentials.Add(new VisualStudioCredential()); + } + + if (authTypes.HasFlag(SymbolsAuthenticationType.Interactive)) + { + credentials.Add(new InteractiveBrowserCredential()); + } + + // If no credentials are specified, default to Interactive + if (credentials.Count == 0) + { + credentials.Add(new InteractiveBrowserCredential()); + } + + return new ChainedTokenCredential(credentials.ToArray()); + } + } +#endif +} diff --git a/src/Utilities/Utilities.csproj b/src/Utilities/Utilities.csproj index 3692e20ce..fffd3a49b 100644 --- a/src/Utilities/Utilities.csproj +++ b/src/Utilities/Utilities.csproj @@ -21,6 +21,10 @@ true + + + + From 51c33434405650631ce22b69db120664539fe040 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Mon, 18 Aug 2025 13:44:24 -0700 Subject: [PATCH 027/148] Add a themed dialog (#2276) Theming isn't easy for the system message box and requires using undocumented APIs. To get theming, including a simple themed message box that roughly follows the existing layout. Move the project to the standard SDK which enables using source generators. The existing SDK was a hack for building before WPF and WinForms were officially released on .NET Core. Push the C# version to 12 for collections expressions, default parameters for lambdas, etc. Add CsWin32 for PInvoke source generation. Add PInvokes for getting the modern shell icons for the new dialog. System.Drawing has the new Icons in .NET, but we're still targetting 4.6.2. (Note that some PInvokes have to still be manually defined because we're targetting AnyCPU. Some headers in Windows are defined with nonstandard packing, which makes the SDK generate metadata that is platform specific. For safety they don't generate any of these and they must be manually evaluated. The manual ones I added come from System.Drawing. If we targetted specific architectures this wouldn't be necessary.) If we're able to move to .NET Core in the future we can get more explicit, coherent theming support through WPF or MAUI. --- global.json | 5 - src/Directory.Build.props | 2 +- src/Directory.Packages.props | 8 +- src/PerfView/App.cs | 22 ++- src/PerfView/CommandProcessor.cs | 31 ++-- src/PerfView/Dialogs/ImageHelpers.cs | 67 ++++++++ .../Dialogs/ManagePresetsDialog.xaml.cs | 2 +- src/PerfView/Dialogs/MemoryDataDialog.xaml.cs | 11 +- src/PerfView/Dialogs/MessageBoxWindow.xaml | 51 ++++++ src/PerfView/Dialogs/MessageBoxWindow.xaml.cs | 71 ++++++++ src/PerfView/Dialogs/NewPresetDialog.xaml.cs | 13 +- src/PerfView/Dialogs/XamlMessageBox.cs | 58 +++++++ src/PerfView/HeapView/IssueView.cs | 3 +- src/PerfView/MainWindow.xaml.cs | 53 ++++-- src/PerfView/NativeMethods.json | 12 ++ src/PerfView/NativeMethods.txt | 4 + src/PerfView/PerfView.csproj | 13 +- src/PerfView/PerfViewData.cs | 157 +++++++++++------- src/PerfView/StackViewer/StackWindow.xaml.cs | 9 +- src/PerfView/Themes/DarkTheme.xaml | 3 +- src/PerfView/Themes/LightTheme.xaml | 6 + 21 files changed, 481 insertions(+), 120 deletions(-) delete mode 100644 global.json create mode 100644 src/PerfView/Dialogs/ImageHelpers.cs create mode 100644 src/PerfView/Dialogs/MessageBoxWindow.xaml create mode 100644 src/PerfView/Dialogs/MessageBoxWindow.xaml.cs create mode 100644 src/PerfView/Dialogs/XamlMessageBox.cs create mode 100644 src/PerfView/NativeMethods.json create mode 100644 src/PerfView/NativeMethods.txt diff --git a/global.json b/global.json deleted file mode 100644 index 2120c2f14..000000000 --- a/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "msbuild-sdks": { - "MSBuild.Sdk.Extras": "1.6.65" - } -} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7814d0993..abd3a52a8 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,7 +6,7 @@ - 11.0 + 12.0 strict diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 1a545da2b..810437ae4 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,18 +1,16 @@ - true - + - @@ -41,12 +39,10 @@ - - @@ -57,10 +53,8 @@ - - \ No newline at end of file diff --git a/src/PerfView/App.cs b/src/PerfView/App.cs index 15b56d4eb..d4b206a87 100755 --- a/src/PerfView/App.cs +++ b/src/PerfView/App.cs @@ -3,6 +3,10 @@ using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Session; using Microsoft.Diagnostics.Utilities; + +#if !PERFVIEW_COLLECT +using PerfView.Dialogs; +#endif using PerfView.Properties; using System; using System.Diagnostics; @@ -43,9 +47,9 @@ public static int Main(string[] args) CommandProcessor = new CommandProcessor(); App.SetAccessibilitySwitchOverrides(); - StreamWriter writerToCleanup = null; // If we create a log file, we need to clean it up. + StreamWriter writerToCleanup = null; // If we create a log file, we need to clean it up. int retCode = -1; - bool newConsoleCreated = false; // If we create a new console, we need to wait before existing + bool newConsoleCreated = false; // If we create a new console, we need to wait before existing try { #if !PERFVIEW_COLLECT @@ -80,8 +84,8 @@ public static int Main(string[] args) DisplaySplashScreen(); } #endif - App.Unpack(); // Install the program if it is not done already - App.RelaunchIfNeeded(args); // If we are running from a a network share, relaunch locally. + App.Unpack(); // Install the program if it is not done already + App.RelaunchIfNeeded(args); // If we are running from a a network share, relaunch locally. // This does the real work retCode = DoMain(args, ref newConsoleCreated, ref writerToCleanup); @@ -891,10 +895,13 @@ public static SymbolReader GetSymbolReader(string etlFilePath = null, SymbolRead #if !PERFVIEW_COLLECT if (!App.CommandLineArgs.TrustPdbs) { - ret.SecurityCheck = delegate (string pdbFile) + ret.SecurityCheck = pdbFile => { - var result = System.Windows.MessageBox.Show("Found " + pdbFile + " on your local machine. Do you want to use it?", - "Security Check", System.Windows.MessageBoxButton.YesNo); + var result = XamlMessageBox.Show( + $"Found {pdbFile} on your local machine. Do you want to use it?", + "Security Check", + System.Windows.MessageBoxButton.YesNo); + return result == System.Windows.MessageBoxResult.Yes; }; } @@ -1265,6 +1272,7 @@ protected override void Dispose(bool disposing) m_terseLog.Dispose(); m_verboseLog.Dispose(); } + #region private private TextWriter m_verboseLog; private TextWriter m_terseLog; diff --git a/src/PerfView/CommandProcessor.cs b/src/PerfView/CommandProcessor.cs index db3fde90b..4b6235ae7 100644 --- a/src/PerfView/CommandProcessor.cs +++ b/src/PerfView/CommandProcessor.cs @@ -7,6 +7,9 @@ using Microsoft.Diagnostics.Tracing.Session; using Microsoft.Diagnostics.Utilities; using Microsoft.Win32; +#if !PERFVIEW_COLLECT +using PerfView.Dialogs; +#endif using System; using System.Collections.Generic; using System.Diagnostics; @@ -17,7 +20,6 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.Windows; using Triggers; using Utilities; using Trigger = Triggers.Trigger; @@ -1682,16 +1684,19 @@ internal static void UnZipIfNecessary(ref string inputFileName, TextWriter log, private void InformedAboutSkippingMerge() { #if !PERFVIEW_COLLECT - GuiApp.MainWindow.Dispatcher.BeginInvoke((Action)delegate () + GuiApp.MainWindow.Dispatcher.BeginInvoke(() => { - MessageBox.Show(GuiApp.MainWindow, - "If you are analyzing the data on the same machine on which you collected it, in the future " + - "you can avoid the time it takes to merge and zip the file by unchecking the 'merge' checkbox " + - "on the collection dialog box.\r\n\r\n" + - "Be careful however, PerfView will remember this option from run to run and you will have to " + - "either check the zip checkbox or use the PerfView's zip command if you wish to analyze on another machine.\r\n\r\n" + - "The WPA analyzer requires merging unconditionally, so you must merge if you wish to use that tool.\r\n\n" + - "See the 'Merging' section in the users guide for complete details.", + XamlMessageBox.Show( + GuiApp.MainWindow, + """ + If you are analyzing the data on the same machine on which you collected it, in the future you can avoid the time it takes to merge and zip the file by unchecking the 'merge' checkbox on the collection dialog box. + + Be careful however, PerfView will remember this option from run to run and you will have to either check the zip checkbox or use the PerfView's zip command if you wish to analyze on another machine. + + The WPA analyzer requires merging unconditionally, so you must merge if you wish to use that tool. + + See the 'Merging' section in the users guide for complete details. + """, "Skip Merging/Zipping for faster local processing."); }); #endif @@ -3311,11 +3316,11 @@ private void ShowAspNetWarningBox(string message) { #if !PERFVIEW_COLLECT // Are we activating with the GUI, then pop a dialog box - if (App.CommandLineArgs.LogFile == null && GuiApp.MainWindow != null) + if (App.CommandLineArgs.LogFile is null) { - GuiApp.MainWindow.Dispatcher.BeginInvoke((Action)delegate () + GuiApp.MainWindow?.Dispatcher.BeginInvoke(() => { - MessageBox.Show(GuiApp.MainWindow, message, "Warning ASP.NET Tracing not installed"); + XamlMessageBox.Show(GuiApp.MainWindow, message, "Warning ASP.NET Tracing not installed"); }); } #endif diff --git a/src/PerfView/Dialogs/ImageHelpers.cs b/src/PerfView/Dialogs/ImageHelpers.cs new file mode 100644 index 000000000..bccd49a0d --- /dev/null +++ b/src/PerfView/Dialogs/ImageHelpers.cs @@ -0,0 +1,67 @@ +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Windows.Win32.UI.WindowsAndMessaging; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Shell; +using System.Windows.Interop; +using System.Runtime.CompilerServices; +using System; + +namespace PerfView.Dialogs; + +internal static class ImageHelpers +{ + /// + /// Gets the for the specified . + /// + /// + /// + /// This method reurns the modern version of the stock icons used in message boxes. + /// + /// + public static ImageSource ToImageSource(MessageBoxImage image) => image switch + { + MessageBoxImage.Error => GetStockIcon(SHSTOCKICONID.SIID_ERROR), + MessageBoxImage.Information => GetStockIcon(SHSTOCKICONID.SIID_INFO), + MessageBoxImage.Warning => GetStockIcon(SHSTOCKICONID.SIID_WARNING), + MessageBoxImage.Question => GetStockIcon(SHSTOCKICONID.SIID_HELP), + _ => throw new ArgumentOutOfRangeException(nameof(image)), + }; + + private static unsafe ImageSource GetStockIcon(SHSTOCKICONID stockIcon, SHGSI_FLAGS options = default) + { + // Note that we don't explicitly check for invalid StockIconId to allow for accessing newer ids introduced + // in later OSes. The HRESULT returned for undefined ids gets converted to an ArgumentException. + + SHSTOCKICONINFO info = new() + { + cbSize = (uint)Unsafe.SizeOf(), + }; + + HRESULT result = SHGetStockIconInfo(stockIcon, options | SHGSI_FLAGS.SHGSI_ICON, &info); + + // This only throws if there is an error. + Marshal.ThrowExceptionForHR((int)result); + + return Imaging.CreateBitmapSourceFromHIcon(info.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); + } + + // This can't be imported in CsWin32 as it technically isn't the same on both X86 and X64 due to a packing of 1 byte on X86. + // For our purposes this is fine as the single definition's layout (SHSTOCKICONINFO) is the same on both platforms. + + [DllImport("Shell32.dll", ExactSpelling = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern unsafe HRESULT SHGetStockIconInfo(SHSTOCKICONID siid, SHGSI_FLAGS uFlags, SHSTOCKICONINFO* psii); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private unsafe struct SHSTOCKICONINFO + { + public uint cbSize; + public HICON hIcon; + public int iSysImageIndex; + public int iIcon; + public fixed char szPath[260]; + } +} diff --git a/src/PerfView/Dialogs/ManagePresetsDialog.xaml.cs b/src/PerfView/Dialogs/ManagePresetsDialog.xaml.cs index b5c834a0e..ad43823b3 100644 --- a/src/PerfView/Dialogs/ManagePresetsDialog.xaml.cs +++ b/src/PerfView/Dialogs/ManagePresetsDialog.xaml.cs @@ -58,7 +58,7 @@ private void SaveClicked(object sender, RoutedEventArgs e) { if (Presets.Exists(x => x.Name == PresetName.Text)) { - MessageBox.Show( + XamlMessageBox.Show( $"Preset '{PresetName.Text}' already exists. Choose another name.", "Preset Name", MessageBoxButton.OK, diff --git a/src/PerfView/Dialogs/MemoryDataDialog.xaml.cs b/src/PerfView/Dialogs/MemoryDataDialog.xaml.cs index 4acef05ff..000edb630 100644 --- a/src/PerfView/Dialogs/MemoryDataDialog.xaml.cs +++ b/src/PerfView/Dialogs/MemoryDataDialog.xaml.cs @@ -148,10 +148,15 @@ private void DumpHeap(bool closeOnComplete) if (m_args.MaxDumpCountK >= 10000) { - var response = MessageBox.Show("WARNING: you have selected a Max Dump Count larger than 10M objects.\r\n" + - "You should only need 100K to do a good job, even at 10M the GUI will be very sluggish.\r\n" + - "Consider canceling and picking a smaller value.", "Max Dump Size Too Big", + var response = XamlMessageBox.Show( + """ + WARNING: you have selected a Max Dump Count larger than 10M objects. + You should only need 100K to do a good job, even at 10M the GUI will be very sluggish. + Consider canceling and picking a smaller value. + """, + "Max Dump Size Too Big", MessageBoxButton.OKCancel); + if (response != MessageBoxResult.OK) { StatusBar.Log("Memory collection canceled."); diff --git a/src/PerfView/Dialogs/MessageBoxWindow.xaml b/src/PerfView/Dialogs/MessageBoxWindow.xaml new file mode 100644 index 000000000..e9988c7fb --- /dev/null +++ b/src/PerfView/Dialogs/MessageBoxWindow.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/PerfView/Dialogs/MessageBoxWindow.xaml.cs b/src/PerfView/Dialogs/MessageBoxWindow.xaml.cs new file mode 100644 index 000000000..ead1901b1 --- /dev/null +++ b/src/PerfView/Dialogs/MessageBoxWindow.xaml.cs @@ -0,0 +1,71 @@ +using System.Windows; +using System.Windows.Controls; + +namespace PerfView.Dialogs; + +/// +/// Simple themed message box window. +/// +internal partial class MessageBoxWindow : Window +{ + public MessageBoxResult Result { get; private set; } + + public MessageBoxWindow(string message, string caption, MessageBoxButton buttons, MessageBoxImage icon, MessageBoxResult defaultResult) + { + InitializeComponent(); + Title = caption; + MessageTextBlock.Text = message; + ConfigureIcon(icon); + ConfigureButtons(buttons, defaultResult); + } + + private void ConfigureIcon(MessageBoxImage icon) + { + // Map MessageBoxImage to SystemIcons or resources + switch (icon) + { + case MessageBoxImage.None: + IconImage.Visibility = Visibility.Collapsed; + break; + default: + IconImage.Source = ImageHelpers.ToImageSource(icon); + break; + } + } + + private void ConfigureButtons(MessageBoxButton buttons, MessageBoxResult defaultResult) + { + ButtonsPanel.Children.Clear(); + foreach ((string Text, MessageBoxResult Result) in Get(buttons)) + { + Button button = new() + { + Content = Text, + Tag = Result, + IsDefault = Result == defaultResult, + IsCancel = Result == MessageBoxResult.Cancel + }; + + button.Click += Button_Click; + ButtonsPanel.Children.Add(button); + } + + static (string Text, MessageBoxResult Result)[] Get(MessageBoxButton buttons) => buttons switch + { + MessageBoxButton.OKCancel => [("_OK", MessageBoxResult.OK), ("_Cancel", MessageBoxResult.Cancel)], + MessageBoxButton.YesNo => [("_Yes", MessageBoxResult.Yes), ("_No", MessageBoxResult.No)], + MessageBoxButton.YesNoCancel => + [("_Yes", MessageBoxResult.Yes), ("_No", MessageBoxResult.No), ("_Cancel", MessageBoxResult.Cancel)], + _ => [("_OK", MessageBoxResult.OK)], + }; + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if (sender is Button button && button.Tag is MessageBoxResult result) + { + Result = result; + DialogResult = true; + } + } +} diff --git a/src/PerfView/Dialogs/NewPresetDialog.xaml.cs b/src/PerfView/Dialogs/NewPresetDialog.xaml.cs index f5f532b35..880ad2bca 100644 --- a/src/PerfView/Dialogs/NewPresetDialog.xaml.cs +++ b/src/PerfView/Dialogs/NewPresetDialog.xaml.cs @@ -30,11 +30,14 @@ private void OKClicked(object sender, RoutedEventArgs e) // Check uniqueness of the name and ask if user wants to continue if (m_existingPresets.Exists(x => x == PresetNameTextBox.Text)) { - if (MessageBox.Show( - $"Preset {PresetNameTextBox.Text} already exists in the list of presets.\r\nDo you want to overwrite it?", - "Preset Name", - MessageBoxButton.OKCancel, - MessageBoxImage.Warning) == MessageBoxResult.Cancel) + if (XamlMessageBox.Show( + $""" + Preset {PresetNameTextBox.Text} already exists in the list of presets. + Do you want to overwrite it? + """, + "Preset Name", + MessageBoxButton.OKCancel, + MessageBoxImage.Warning) == MessageBoxResult.Cancel) { return; } diff --git a/src/PerfView/Dialogs/XamlMessageBox.cs b/src/PerfView/Dialogs/XamlMessageBox.cs new file mode 100644 index 000000000..bbe118d5e --- /dev/null +++ b/src/PerfView/Dialogs/XamlMessageBox.cs @@ -0,0 +1,58 @@ +using System.Windows; + +namespace PerfView.Dialogs; + +/// +/// Themed replacement for that uses a custom XAML window. +/// +public static class XamlMessageBox +{ + /// + public static MessageBoxResult Show(string message) + => Show(null, message, string.Empty, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK); + + /// + public static MessageBoxResult Show(string message, string caption) + => Show(null, message, caption, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK); + + /// + public static MessageBoxResult Show(string message, string caption, MessageBoxButton buttons) + => Show(null, message, caption, buttons, MessageBoxImage.None, MessageBoxResult.OK); + + /// + public static MessageBoxResult Show(string message, string caption, MessageBoxButton buttons, MessageBoxImage icon) + => Show(null, message, caption, buttons, icon, MessageBoxResult.OK); + + /// + public static MessageBoxResult Show(string message, string caption, MessageBoxButton buttons, MessageBoxImage icon, MessageBoxResult defaultResult) + => Show(null, message, caption, buttons, icon, defaultResult); + + /// + public static MessageBoxResult Show(Window owner, string message) + => Show(owner, message, string.Empty, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK); + + /// + public static MessageBoxResult Show(Window owner, string message, string caption) + => Show(owner, message, caption, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK); + + /// + public static MessageBoxResult Show(Window owner, string message, string caption, MessageBoxButton buttons) + => Show(owner, message, caption, buttons, MessageBoxImage.None, MessageBoxResult.OK); + + /// + public static MessageBoxResult Show(Window owner, string message, string caption, MessageBoxButton buttons, MessageBoxImage icon) + => Show(owner, message, caption, buttons, icon, MessageBoxResult.OK); + + /// + public static MessageBoxResult Show(Window owner, string message, string caption, MessageBoxButton buttons, MessageBoxImage icon, MessageBoxResult defaultResult) + { + MessageBoxWindow window = new(message, caption, buttons, icon, defaultResult); + if (owner is not null) + { + window.Owner = owner; + } + + window.ShowDialog(); + return window.Result; + } +} diff --git a/src/PerfView/HeapView/IssueView.cs b/src/PerfView/HeapView/IssueView.cs index 512550c34..f534eb79d 100644 --- a/src/PerfView/HeapView/IssueView.cs +++ b/src/PerfView/HeapView/IssueView.cs @@ -1,5 +1,6 @@ using Microsoft.Diagnostics.Tracing.Analysis.GC; using Microsoft.Diagnostics.Tracing.Stacks; +using PerfView.Dialogs; using System; using System.Collections.Generic; using System.Windows; @@ -244,7 +245,7 @@ private void OnOpenInducedStacks(object sender, RoutedEventArgs e) if (source.SampleIndexLimit == 0) { - MessageBox.Show("No stacks found for induced GC", ".Net Heap Analyzer", MessageBoxButton.OK); + XamlMessageBox.Show("No stacks found for induced GC", ".Net Heap Analyzer", MessageBoxButton.OK); } else { diff --git a/src/PerfView/MainWindow.xaml.cs b/src/PerfView/MainWindow.xaml.cs index 81560bea6..f9df776cc 100644 --- a/src/PerfView/MainWindow.xaml.cs +++ b/src/PerfView/MainWindow.xaml.cs @@ -16,11 +16,9 @@ using System.Text; using System.Text.RegularExpressions; using System.Windows; -using System.Windows.Automation; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; -using System.Windows.Navigation; using Utilities; namespace PerfView @@ -91,20 +89,35 @@ public MainWindow(bool testing = false) { if (NumWindowsNeedingSaving != 0) { - var result = MessageBox.Show(this, "You have unsaved notes in some Stack Views.\r\nDo you wish to exit anyway?", "Unsaved Data", MessageBoxButton.OKCancel); + var result = XamlMessageBox.Show( + this, + """ + You have unsaved notes in some Stack Views. + Do you wish to exit anyway? + """, + "Unsaved Data", + MessageBoxButton.OKCancel); + if (result == MessageBoxResult.Cancel) { e.Cancel = true; return; } } + if (StatusBar.IsWorking) { if (App.CommandProcessor.StopInProgress) { - var result = MessageBox.Show(this, - "Closing PerfView while the trace is being processed will result in a trace that is unusable if copied off of this machine.\r\nWould you still like to close PerfView?", - "Collecting data in progress", MessageBoxButton.YesNo); + var result = XamlMessageBox.Show( + this, + """ + Closing PerfView while the trace is being processed will result in a trace that is unusable if copied off of this machine. + Would you still like to close PerfView? + """, + "Collecting data in progress", + MessageBoxButton.YesNo); + if (result == MessageBoxResult.No) { e.Cancel = true; @@ -700,8 +713,12 @@ internal void DoUserCommandHelp(object sender, RoutedEventArgs e) private void DoAbout(object sender, RoutedEventArgs e) { - string versionString = "PerfView Version " + AppInfo.VersionNumber + " \r\nBuildDate: " + AppInfo.BuildDate; - MessageBox.Show(versionString, versionString); + string versionString = $""" + PerfView Version {AppInfo.VersionNumber} + BuildDate: {AppInfo.BuildDate} + """; + + XamlMessageBox.Show(versionString, versionString); } // Gui actions in the TreeView pane @@ -826,8 +843,11 @@ private void DoDelete(object sender, ExecutedRoutedEventArgs e) throw new ApplicationException("No file selected."); } - var response = MessageBox.Show(this, - "Delete " + Path.GetFileName(selectedFile.FilePath) + "?", "Delete Confirmation", MessageBoxButton.OKCancel); + var response = XamlMessageBox.Show( + this, + $"Delete {Path.GetFileName(selectedFile.FilePath)}?", + "Delete Confirmation", + MessageBoxButton.OKCancel); // TODO does not work with the unmerged files if (response == MessageBoxResult.OK) @@ -1297,9 +1317,14 @@ private bool AllowNavigateToWeb m_AllowNavigateToWeb = allowNavigateToWeb == "true"; if (!m_AllowNavigateToWeb) { - var result = MessageBox.Show( - "PerfView is about to open content on the web.\r\nIs this OK?", - "Navigate to Web", MessageBoxButton.YesNo); + var result = XamlMessageBox.Show( + """ + PerfView is about to open content on the web. + Is this OK? + """, + "Navigate to Web", + MessageBoxButton.YesNo); + if (result == MessageBoxResult.Yes) { m_AllowNavigateToWeb = true; @@ -1424,7 +1449,7 @@ private void SetTheme_Executed(object sender, ExecutedRoutedEventArgs e) Theme theme = ((ThemeViewModel.SetThemeCommand)e.Command).Theme; ThemeViewModel.SetTheme(theme); - MessageBox.Show("Restart PerfView to apply theme changes."); + XamlMessageBox.Show("Restart PerfView to apply theme changes."); e.Handled = true; } diff --git a/src/PerfView/NativeMethods.json b/src/PerfView/NativeMethods.json new file mode 100644 index 000000000..fa89f2f3a --- /dev/null +++ b/src/PerfView/NativeMethods.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "public": false, + "allowMarshaling": false, + "useSafeHandles": false, + "className": "PInvoke", + "comInterop": { + "preserveSigMethods": [ + "*" + ] + } +} \ No newline at end of file diff --git a/src/PerfView/NativeMethods.txt b/src/PerfView/NativeMethods.txt new file mode 100644 index 000000000..c402ef249 --- /dev/null +++ b/src/PerfView/NativeMethods.txt @@ -0,0 +1,4 @@ +SHSTOCKICONID +SHGSI_FLAGS +HICON +HRESULT \ No newline at end of file diff --git a/src/PerfView/PerfView.csproj b/src/PerfView/PerfView.csproj index 21f2f431c..aa2492bb7 100644 --- a/src/PerfView/PerfView.csproj +++ b/src/PerfView/PerfView.csproj @@ -1,5 +1,5 @@  - + net462 @@ -9,12 +9,13 @@ true true false + true PerfView Microsoft Copyright © Microsoft 2010 $(PerfViewVersion) - + AnyCPU @@ -72,7 +73,7 @@ - + @@ -95,6 +96,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -768,6 +773,8 @@ + + diff --git a/src/PerfView/PerfViewData.cs b/src/PerfView/PerfViewData.cs index 4f0458cc3..4aed50cdd 100644 --- a/src/PerfView/PerfViewData.cs +++ b/src/PerfView/PerfViewData.cs @@ -43,6 +43,7 @@ using Utilities; using Address = System.UInt64; using EventSource = EventSources.EventSource; +using PerfView.Dialogs; namespace PerfView { @@ -4670,13 +4671,16 @@ public override void Open(Window parentWindow, StatusBar worker, Action doAfter // Catch the error if you don't merge and move to a new machine. if (traceLog != null && !traceLog.CurrentMachineIsCollectionMachine() && !traceLog.HasPdbInfo) { - MessageBox.Show(parentWindow, - "Warning! This file was not merged and was moved from the collection\r\n" + - "machine. This means the data is incomplete and symbolic name resolution\r\n" + - "will NOT work. The recommended fix is use the perfview (not windows OS)\r\n" + - "zip command. Right click on the file in the main view and select ZIP.\r\n" + - "\r\n" + - "See merging and zipping in the users guide for more information.", + XamlMessageBox.Show( + parentWindow, + """ + Warning! This file was not merged and was moved from the collection + machine. This means the data is incomplete and symbolic name resolution + will NOT work. The recommended fix is use the perfview (not windows OS) + zip command. Right click on the file in the main view and select ZIP. + + See merging and zipping in the users guide for more information. + """, "Data not merged before leaving the machine!"); } @@ -4783,14 +4787,19 @@ private static bool WarnAboutBrokenStacks(Window parentWindow, float brokenPerce { if (brokenPercent > 1) { - log.WriteLine("Finished aggregating stacks. (" + brokenPercent.ToString("f1") + "% Broken Stacks)"); + log.WriteLine($"Finished aggregating stacks. ({brokenPercent:f1}% Broken Stacks)"); } if (brokenPercent > 10) { - MessageBox.Show(parentWindow, "Warning: There are " + brokenPercent.ToString("f1") + "% stacks that are broken\r\n" + - "Top down analysis is suspect, however bottom up approaches are still valid.\r\n\r\n" + - "Use the troubleshooting link at the top of the view for more information.\r\n", + XamlMessageBox.Show( + parentWindow, + $""" + Warning: There are {brokenPercent:f1}% stacks that are broken. + Top down analysis is suspect, however bottom up approaches are still valid. + + Use the troubleshooting link at the top of the view for more information. + """, "Broken Stacks"); return true; @@ -5594,12 +5603,15 @@ protected internal override StackSource OpenStackSourceImpl(string streamName, T { // TODO FIX NOW, investigate the missing events. All we know is that incs and dec are not // consistent with the RefCount value that is in the events. - GuiApp.MainWindow.Dispatcher.BeginInvoke((Action)delegate () - { - MessageBox.Show(GuiApp.MainWindow, - "Warning: the Interop CCW events on which this data is based seem to be incomplete.\r\n" + - "There seem to be missing instrumentation, which make the referenct counts unreliable\r\n" - , "Data May be Incorrect"); + GuiApp.MainWindow.Dispatcher.BeginInvoke(() => + { + MessageBox.Show( + GuiApp.MainWindow, + """ + Warning: the Interop CCW events on which this data is based seem to be incomplete. + There seem to be missing instrumentation, which make the referenct counts unreliable + """, + "Data May be Incorrect"); }); var objectToTypeMap = new Dictionary(1000); @@ -7574,14 +7586,17 @@ protected internal override void ConfigureStackWindow(string stackSourceName, St { if (App.UserConfigData["WarnedAboutOsHeapAllocTypes"] == null) { - MessageBox.Show(stackWindow, - "Warning: Allocation type resolution only happens on window launch.\r\n" + - "Thus if you manually lookup symbols in this view you will get method\r\n" + - "names of allocations sites, but to get the type name associated the \r\n" + - "allocation site.\r\n" + - "\r\n" + - "You must close and reopen this window to get the allocation types.\r\n" - , "May need to resolve PDBs and reopen."); + XamlMessageBox.Show( + stackWindow, + """ + Warning: Allocation type resolution only happens on window launch. + Thus if you manually lookup symbols in this view you will get method + names of allocations sites, but to get the type name associated the + allocation site. + + You must close and reopen this window to get the allocation types. + """, + "May need to resolve PDBs and reopen."); App.UserConfigData["WarnedAboutOsHeapAllocTypes"] = "true"; } } @@ -7662,13 +7677,15 @@ protected override Action OpenImpl(Window parentWindow, StatusBar worker if (!m_notifiedAboutWin8) { m_notifiedAboutWin8 = true; - var versionMismatchWarning = "This trace was captured on Window 8 and is being read\r\n" + - "on and earlier OS. If you experience any problems please\r\n" + - "read the trace on an Windows 8 OS."; + var versionMismatchWarning = """ + This trace was captured on Window 8 and is being read + on and earlier OS. If you experience any problems please + read the trace on an Windows 8 OS. + """; worker.LogWriter.WriteLine(versionMismatchWarning); - parentWindow.Dispatcher.BeginInvoke((Action)delegate () + parentWindow.Dispatcher.BeginInvoke(() => { - MessageBox.Show(parentWindow, versionMismatchWarning, "Log File Version Mismatch", MessageBoxButton.OK); + XamlMessageBox.Show(parentWindow, versionMismatchWarning, "Log File Version Mismatch", MessageBoxButton.OK); }); } } @@ -8274,11 +8291,18 @@ public TraceLog GetTraceLog(TextWriter log, Action onLostEvents m_traceLog.CodeAddresses.UnsafePDBMatching = true; } - if (m_traceLog.Truncated) // Warn about truncation. + if (m_traceLog.Truncated) // Warn about truncation. { - GuiApp.MainWindow.Dispatcher.BeginInvoke((Action)delegate () + GuiApp.MainWindow.Dispatcher.BeginInvoke(() => { - MessageBox.Show("The ETL file was too big to convert and was truncated.\r\nSee log for details", "Log File Truncated", MessageBoxButton.OK); + XamlMessageBox.Show( + """ + The ETL file was too big to convert and was truncated. + See log for details. + """, + "Log File Truncated", + MessageBoxButton.OK); + }); } return m_traceLog; @@ -8304,9 +8328,9 @@ private void HandleLostEvents(Window parentWindow, bool truncated, int numberOfL } MessageBoxResult result = MessageBoxResult.None; - parentWindow.Dispatcher.BeginInvoke((Action)delegate () + parentWindow.Dispatcher.BeginInvoke(() => { - result = MessageBox.Show(parentWindow, warning, "Lost Events", MessageBoxButton.OKCancel); + result = XamlMessageBox.Show(parentWindow, warning, "Lost Events", MessageBoxButton.OKCancel); worker.LogWriter.WriteLine(warning); if (result != MessageBoxResult.OK) { @@ -9423,7 +9447,14 @@ public TraceLog GetTraceLog(TextWriter log) { GuiApp.MainWindow.Dispatcher.BeginInvoke((Action)delegate () { - MessageBox.Show("The ETL file was too big to convert and was truncated.\r\nSee log for details", "Log File Truncated", MessageBoxButton.OK); + XamlMessageBox.Show( + """ + The ETL file was too big to convert and was truncated. + See log for details. + """, + "Log File Truncated", + MessageBoxButton.OK); + }); } return m_traceLog; @@ -10201,7 +10232,14 @@ public TraceLog GetTraceLog(TextWriter log, Action onLostEvents { GuiApp.MainWindow.Dispatcher.BeginInvoke((Action)delegate () { - MessageBox.Show("The ETL file was too big to convert and was truncated.\r\nSee log for details", "Log File Truncated", MessageBoxButton.OK); + MessageBox.Show( + """ + The ETL file was too big to convert and was truncated. + See log for details. + """, + "Log File Truncated", + MessageBoxButton.OK); + }); } return m_traceLog; @@ -10300,24 +10338,22 @@ public override void LookupSymbolsForModule(string simpleModuleName, TextWriter private void HandleLostEvents(Window parentWindow, bool truncated, int numberOfLostEvents, int eventCountAtTrucation, StatusBar worker) { - string warning; - if (!truncated) - { - warning = "WARNING: There were " + numberOfLostEvents + " lost events in the trace.\r\n" + - "Some analysis might be invalid."; - } - else - { - warning = "WARNING: The ETLX file was truncated at " + eventCountAtTrucation + " events.\r\n" + - "This is to keep the ETLX file size under 4GB, however all rundown events are processed.\r\n" + - "Use /SkipMSec:XXX after clearing the cache (File->Clear Temp Files) to see the later parts of the file.\r\n" + - "See log for more details."; - } + string warning = !truncated + ? $""" + WARNING: There were {numberOfLostEvents} lost events in the trace. + Some analysis might be invalid. + """ + : $""" + WARNING: The ETLX file was truncated at {eventCountAtTrucation} events. + This is to keep the ETLX file size under 4GB, however all rundown events are processed. + Use /SkipMSec:XXX after clearing the cache (File->Clear Temp Files) to see the later parts of the file. + See log for more details. + """; MessageBoxResult result = MessageBoxResult.None; parentWindow.Dispatcher.BeginInvoke((Action)delegate () { - result = MessageBox.Show(parentWindow, warning, "Lost Events", MessageBoxButton.OKCancel); + result = XamlMessageBox.Show(parentWindow, warning, "Lost Events", MessageBoxButton.OKCancel); worker.LogWriter.WriteLine(warning); if (result != MessageBoxResult.OK) { @@ -10425,17 +10461,22 @@ public string ResolveTypeName(int typeID, Graphs.Module module) { if (m_numFailures == 1 && !Path.GetFileName(module.Path).StartsWith("mrt", StringComparison.OrdinalIgnoreCase)) { - GuiApp.MainWindow.Dispatcher.BeginInvoke((Action)delegate () + GuiApp.MainWindow.Dispatcher.BeginInvoke(() => { - MessageBox.Show(GuiApp.MainWindow, - "Warning: Could not find PDB for module " + Path.GetFileName(module.Path) + "\r\n" + - "Some types will not have symbolic names.\r\n" + - "See log for more details.\r\n" + - "Fix by placing PDB on symbol path or in a directory called 'symbols' beside .gcdump file.", + XamlMessageBox.Show( + GuiApp.MainWindow, + $""" + Warning: Could not find PDB for module {Path.GetFileName(module.Path)}. + Some types will not have symbolic names. + See log for more details. + Fix by placing PDB on symbol path or in a directory called 'symbols' beside .gcdump file. + """, "PDB lookup failure"); }); } - m_log.WriteLine("Failed to find PDB for module {0} to look up type 0x{1:x}", module.Path, typeID); + + m_log.WriteLine($"Failed to find PDB for module {module.Path} to look up type 0x{typeID:x}"); + if (m_numFailures == 5) { m_log.WriteLine("Discontinuing PDB module lookup messages"); diff --git a/src/PerfView/StackViewer/StackWindow.xaml.cs b/src/PerfView/StackViewer/StackWindow.xaml.cs index 73751d3e6..411bf6a9f 100644 --- a/src/PerfView/StackViewer/StackWindow.xaml.cs +++ b/src/PerfView/StackViewer/StackWindow.xaml.cs @@ -3115,7 +3115,14 @@ private void FinishInit() if (m_ViewsShouldBeSaved) { - var result = MessageBox.Show("You have created Notes that have not been saved\r\nDo you wish to save?", "Unsaved Notes", MessageBoxButton.YesNoCancel); + var result = XamlMessageBox.Show( + """ + You have created Notes that have not been saved. + Do you wish to save? + """, + "Unsaved Notes", + MessageBoxButton.YesNoCancel); + if (result == MessageBoxResult.Cancel) { e.Cancel = true; diff --git a/src/PerfView/Themes/DarkTheme.xaml b/src/PerfView/Themes/DarkTheme.xaml index 05746196c..920efd0ed 100644 --- a/src/PerfView/Themes/DarkTheme.xaml +++ b/src/PerfView/Themes/DarkTheme.xaml @@ -4252,6 +4252,7 @@ SOFTWARE. + - private static readonly System.Text.RegularExpressions.Regex s_jittedSymbolRegex = - new System.Text.RegularExpressions.Regex(@"^(?.+?)\s+\[(?[^\]]+)\]\s+(?.+?)\[(?[^\]]+)\]$", - System.Text.RegularExpressions.RegexOptions.Compiled); + private static readonly Regex s_jittedSymbolRegex = + new Regex(@"^(?.+?)\s+\[(?[^\]]+)\]\s+(?.+?)\[(?[^\]]+)\]$", + RegexOptions.Compiled); /// /// Parses a dotnet jitted symbol name from universal traces with format: "returnType [module] Namespace.Class::Method(args...)[OptimizationLevel]" From 186e9ffed353dc6e92815958e1e247bc4db28846 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:06:25 -0700 Subject: [PATCH 036/148] Fix hyperlink focus visibility in dark mode and improve keyboard navigation (#2295) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com> Co-authored-by: Brian Robbins --- src/PerfView/Themes/DarkTheme.xaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PerfView/Themes/DarkTheme.xaml b/src/PerfView/Themes/DarkTheme.xaml index 920efd0ed..bb00bb701 100644 --- a/src/PerfView/Themes/DarkTheme.xaml +++ b/src/PerfView/Themes/DarkTheme.xaml @@ -2209,6 +2209,7 @@ SOFTWARE. - + diff --git a/src/PerfView/Themes/DarkTheme.xaml b/src/PerfView/Themes/DarkTheme.xaml index bb00bb701..01f4a16d8 100644 --- a/src/PerfView/Themes/DarkTheme.xaml +++ b/src/PerfView/Themes/DarkTheme.xaml @@ -62,6 +62,7 @@ SOFTWARE. + @@ -111,6 +112,20 @@ SOFTWARE. + + + diff --git a/src/PerfView/Themes/LightTheme.xaml b/src/PerfView/Themes/LightTheme.xaml index e26f9e4e2..8da12ab28 100644 --- a/src/PerfView/Themes/LightTheme.xaml +++ b/src/PerfView/Themes/LightTheme.xaml @@ -63,6 +63,7 @@ SOFTWARE. + @@ -71,6 +72,20 @@ SOFTWARE. + + +