From 8deda8e01f228045e8e5cd59cd3f738cf15a1896 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 May 2025 03:59:07 +0000 Subject: [PATCH 1/9] Initial plan for issue From 571dbbd48416d0754ab72e629c7c87d2bf7b2bf5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 May 2025 04:06:13 +0000 Subject: [PATCH 2/9] Add MemoryAnalyzer to EventPipePerfViewData to enable Heap Analyzer for .nettrace files Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com> --- src/PerfView/PerfViewData.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PerfView/PerfViewData.cs b/src/PerfView/PerfViewData.cs index 2c1dd87b6..e63886b21 100644 --- a/src/PerfView/PerfViewData.cs +++ b/src/PerfView/PerfViewData.cs @@ -9562,6 +9562,8 @@ protected override Action OpenImpl(Window parentWindow, StatusBar worker memory.AddChild(new PerfViewGCStats(this)); } + memory.AddChild(new MemoryAnalyzer(this)); + if (hasGCAllocationTicks) { if (hasObjectUpdate) From 75be7c2b283e0667f2e90628deec571ddf5107ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 May 2025 04:16:01 +0000 Subject: [PATCH 3/9] Move MemoryAnalyzer inside hasGC conditional block Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com> --- src/PerfView/PerfViewData.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PerfView/PerfViewData.cs b/src/PerfView/PerfViewData.cs index e63886b21..5759421fe 100644 --- a/src/PerfView/PerfViewData.cs +++ b/src/PerfView/PerfViewData.cs @@ -9560,10 +9560,9 @@ protected override Action OpenImpl(Window parentWindow, StatusBar worker if (hasGC) { memory.AddChild(new PerfViewGCStats(this)); + memory.AddChild(new MemoryAnalyzer(this)); } - memory.AddChild(new MemoryAnalyzer(this)); - if (hasGCAllocationTicks) { if (hasObjectUpdate) From fc742bf816ceb987dd759389b88c095d7b2e8aaf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 May 2025 17:48:22 +0000 Subject: [PATCH 4/9] Fix MemoryAnalyzer.Open to support non-ETL formats and single-process files Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com> --- src/PerfView/HeapView/HeapView.cs | 47 ++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/PerfView/HeapView/HeapView.cs b/src/PerfView/HeapView/HeapView.cs index fbd420b52..fab3b7d29 100644 --- a/src/PerfView/HeapView/HeapView.cs +++ b/src/PerfView/HeapView/HeapView.cs @@ -95,6 +95,29 @@ private Menu CreateMainMenu() return menu; } + private TraceLog GetTraceLog(StatusBar worker) + { + var etlDataFile = m_dataFile as ETLPerfViewData; + if (etlDataFile != null) + { + return etlDataFile.GetTraceLog(worker.LogWriter); + } + + var linuxDataFile = m_dataFile as LinuxPerfViewData; + if (linuxDataFile != null) + { + return linuxDataFile.GetTraceLog(worker.LogWriter); + } + + var eventPipeDataFile = m_dataFile as EventPipePerfViewData; + if (eventPipeDataFile != null) + { + return eventPipeDataFile.GetTraceLog(worker.LogWriter); + } + + return null; + } + private void LaunchViewer(List selectedProcesses) { // Single process only @@ -183,21 +206,31 @@ public override void Open(Window parentWindow, StatusBar worker, Action doAfter { if (m_dataFile.SupportsProcesses) { - // Only ETL/ETLX file supported - ETLPerfViewData etlDataFile = m_dataFile as ETLPerfViewData; + // Get TraceLog from the data file (supports multiple formats) + m_traceLog = GetTraceLog(worker); - if (etlDataFile == null) + if (m_traceLog != null) { - return; - } + m_worker = worker; - m_traceLog = etlDataFile.GetTraceLog(worker.LogWriter); + m_traceLog.SelectClrProcess(LaunchViewer); + } + } + else + { + // Single process format - get the process directly + m_traceLog = GetTraceLog(worker); if (m_traceLog != null) { m_worker = worker; - m_traceLog.SelectClrProcess(LaunchViewer); + bool hasClr; + var clrProcesses = m_traceLog.GetClrProcesses(out hasClr); + if (clrProcesses.Count > 0) + { + LaunchViewer(clrProcesses); + } } } } From 6bae0c0bb7439d6911ac7ac2f5d8c7fe013e9646 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 May 2025 18:16:28 +0000 Subject: [PATCH 5/9] Fix Toolbox.GetClrProcesses to use full file path for non-clr modules Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com> --- src/PerfView/HeapView/Toolbox.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/PerfView/HeapView/Toolbox.cs b/src/PerfView/HeapView/Toolbox.cs index 9cf658245..3b3b53dd3 100644 --- a/src/PerfView/HeapView/Toolbox.cs +++ b/src/PerfView/HeapView/Toolbox.cs @@ -787,19 +787,24 @@ internal static List GetClrProcesses(this TraceLog traceLog, out bool { clrVersion = m.ModuleFile.FilePath; - int pos = clrVersion.LastIndexOf('\\'); - - if (pos > 0) + // Only extract version from path for clr.dll, for other modules use full path + if (name.Equals("clr", StringComparison.OrdinalIgnoreCase)) { - int p = clrVersion.LastIndexOf('\\', pos - 1); + int pos = clrVersion.LastIndexOf('\\'); - if (p > 0) + if (pos > 0) { - pos = p; - } + int p = clrVersion.LastIndexOf('\\', pos - 1); - clrVersion = clrVersion.Substring(pos + 1); + if (p > 0) + { + pos = p; + } + + clrVersion = clrVersion.Substring(pos + 1); + } } + // For non-clr modules, clrVersion is already set to the full file path above } hasClr = true; From 05ed532affe1301291f989e588f8e7d35490561f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Jun 2025 20:50:06 +0000 Subject: [PATCH 6/9] Refactor code based on review feedback: use pattern matching and simplify path logic Co-authored-by: marklio <529657+marklio@users.noreply.github.com> --- src/PerfView/HeapView/HeapView.cs | 25 ++++++------------------- src/PerfView/HeapView/Toolbox.cs | 14 +++----------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/PerfView/HeapView/HeapView.cs b/src/PerfView/HeapView/HeapView.cs index fab3b7d29..898ca7c99 100644 --- a/src/PerfView/HeapView/HeapView.cs +++ b/src/PerfView/HeapView/HeapView.cs @@ -97,25 +97,13 @@ private Menu CreateMainMenu() private TraceLog GetTraceLog(StatusBar worker) { - var etlDataFile = m_dataFile as ETLPerfViewData; - if (etlDataFile != null) + return m_dataFile switch { - return etlDataFile.GetTraceLog(worker.LogWriter); - } - - var linuxDataFile = m_dataFile as LinuxPerfViewData; - if (linuxDataFile != null) - { - return linuxDataFile.GetTraceLog(worker.LogWriter); - } - - var eventPipeDataFile = m_dataFile as EventPipePerfViewData; - if (eventPipeDataFile != null) - { - return eventPipeDataFile.GetTraceLog(worker.LogWriter); - } - - return null; + ETLPerfViewData etlDataFile => etlDataFile.GetTraceLog(worker.LogWriter), + LinuxPerfViewData linuxDataFile => linuxDataFile.GetTraceLog(worker.LogWriter), + EventPipePerfViewData eventPipeDataFile => eventPipeDataFile.GetTraceLog(worker.LogWriter), + _ => null, + }; } private void LaunchViewer(List selectedProcesses) @@ -237,7 +225,6 @@ public override void Open(Window parentWindow, StatusBar worker, Action doAfter else { m_mainWindow.Focus(); - doAfter?.Invoke(); } } diff --git a/src/PerfView/HeapView/Toolbox.cs b/src/PerfView/HeapView/Toolbox.cs index 3b3b53dd3..86c76d93b 100644 --- a/src/PerfView/HeapView/Toolbox.cs +++ b/src/PerfView/HeapView/Toolbox.cs @@ -790,18 +790,10 @@ internal static List GetClrProcesses(this TraceLog traceLog, out bool // Only extract version from path for clr.dll, for other modules use full path if (name.Equals("clr", StringComparison.OrdinalIgnoreCase)) { - int pos = clrVersion.LastIndexOf('\\'); - - if (pos > 0) + var parentDir = Path.GetDirectoryName(clrVersion); + if (!String.IsNullOrEmpty(parentDir)) { - int p = clrVersion.LastIndexOf('\\', pos - 1); - - if (p > 0) - { - pos = p; - } - - clrVersion = clrVersion.Substring(pos + 1); + clrVersion = Path.GetFileName(parentDir); } } // For non-clr modules, clrVersion is already set to the full file path above From 5b825463eda1f72e63ae73f1ad4bc134356111b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Jun 2025 20:58:26 +0000 Subject: [PATCH 7/9] Remove unused bool hasClr variable, use out _ instead Co-authored-by: marklio <529657+marklio@users.noreply.github.com> --- src/PerfView/HeapView/HeapView.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PerfView/HeapView/HeapView.cs b/src/PerfView/HeapView/HeapView.cs index 898ca7c99..07b05cb18 100644 --- a/src/PerfView/HeapView/HeapView.cs +++ b/src/PerfView/HeapView/HeapView.cs @@ -213,8 +213,7 @@ public override void Open(Window parentWindow, StatusBar worker, Action doAfter { m_worker = worker; - bool hasClr; - var clrProcesses = m_traceLog.GetClrProcesses(out hasClr); + var clrProcesses = m_traceLog.GetClrProcesses(out _); if (clrProcesses.Count > 0) { LaunchViewer(clrProcesses); From 265243cf4532798d7b31a75a210d79703a81f570 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Wed, 4 Jun 2025 17:16:43 -0700 Subject: [PATCH 8/9] Update to C# version 8.0. --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c43f4d8e9..48fefffa0 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,7 +6,7 @@ - 7.3 + 8.0 strict From 69313865bf30a3275ee6155785dc579416069492 Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Wed, 4 Jun 2025 17:32:18 -0700 Subject: [PATCH 9/9] EventPipe traces don't have a runtime module because they only contain managed loads. --- src/PerfView/HeapView/Toolbox.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PerfView/HeapView/Toolbox.cs b/src/PerfView/HeapView/Toolbox.cs index 86c76d93b..190977e25 100644 --- a/src/PerfView/HeapView/Toolbox.cs +++ b/src/PerfView/HeapView/Toolbox.cs @@ -735,6 +735,7 @@ internal static bool IsClr(this string name) { return name.Equals("clr", StringComparison.OrdinalIgnoreCase) || name.Equals("coreclr", StringComparison.OrdinalIgnoreCase) || + name.Equals("libcoreclr", StringComparison.OrdinalIgnoreCase) || name.Equals("mscorwks", StringComparison.OrdinalIgnoreCase) || name.Equals("mrt100", StringComparison.OrdinalIgnoreCase); } @@ -746,6 +747,8 @@ internal static bool IsMscorlib(this string name) { return name.Equals("mscorlib", StringComparison.OrdinalIgnoreCase) || name.Equals("mscorlib.ni", StringComparison.OrdinalIgnoreCase) || + name.Equals("system.private.corelib", StringComparison.OrdinalIgnoreCase) || + name.Equals("system.private.corelib.il", StringComparison.OrdinalIgnoreCase) || name.Equals("corefx", StringComparison.OrdinalIgnoreCase); } @@ -775,8 +778,6 @@ internal static List GetClrProcesses(this TraceLog traceLog, out bool { foreach (TraceLoadedModule m in mods) { - keep = false; - string name = m.Name; if (name.IsClr() || name.IsMscorlib() && String.IsNullOrEmpty(clrVersion))