From 47ce0d26b62e521cd3b09258c51acb8bdfc324f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:29:24 +0000 Subject: [PATCH 01/11] Initial plan From 682a87d29413d7c029c74f3113cb0ee39525d88a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:37:05 +0000 Subject: [PATCH 02/11] Add --diagnostic-port option to dotnet-stack report command Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com> --- src/Tools/dotnet-stack/ReportCommand.cs | 66 +++++++++++++++++++------ 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index c8f2ecc365..c1287843c2 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -27,22 +27,35 @@ internal static class ReportCommandHandler /// The process to report the stack from. /// The name of process to report the stack from. /// The duration of to trace the target for. + /// The diagnostic port to connect to. /// - private static async Task Report(CancellationToken ct, TextWriter stdOutput, TextWriter stdError, int processId, string name, TimeSpan duration) + private static async Task Report(CancellationToken ct, TextWriter stdOutput, TextWriter stdError, int processId, string name, TimeSpan duration, string diagnosticPort) { string tempNetTraceFilename = Path.Join(Path.GetTempPath(), Path.GetRandomFileName() + ".nettrace"); string tempEtlxFilename = ""; try { - // Either processName or processId has to be specified. + // Validate that only one of processId, name, or diagnosticPort is specified + int optionCount = 0; + if (processId != 0) optionCount++; + if (!string.IsNullOrEmpty(name)) optionCount++; + if (!string.IsNullOrEmpty(diagnosticPort)) optionCount++; + + if (optionCount == 0) + { + stdError.WriteLine("--process-id, --name, or --diagnostic-port is required"); + return -1; + } + else if (optionCount > 1) + { + stdError.WriteLine("Only one of --process-id, --name, or --diagnostic-port can be specified"); + return -1; + } + + // Resolve process name to ID if needed if (!string.IsNullOrEmpty(name)) { - if (processId != 0) - { - Console.WriteLine("Can only specify either --name or --process-id option."); - return -1; - } processId = CommandUtils.FindProcessIdWithName(name); if (processId < 0) { @@ -55,14 +68,31 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput stdError.WriteLine("Process ID should not be negative."); return -1; } - else if (processId == 0) + + DiagnosticsClient client; + if (!string.IsNullOrEmpty(diagnosticPort)) { - stdError.WriteLine("--process-id is required"); - return -1; + try + { + IpcEndpointConfig diagnosticPortConfig = IpcEndpointConfig.Parse(diagnosticPort); + if (!diagnosticPortConfig.IsConnectConfig) + { + stdError.WriteLine("--diagnostic-port only supports connect mode to a runtime."); + return -1; + } + client = new DiagnosticsClient(diagnosticPortConfig); + } + catch (Exception ex) + { + stdError.WriteLine($"--diagnostic-port argument error: {ex.Message}"); + return -1; + } + } + else + { + client = new DiagnosticsClient(processId); } - - DiagnosticsClient client = new(processId); List providers = new() { new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) @@ -189,7 +219,8 @@ public static Command ReportCommand() { ProcessIdOption, NameOption, - DurationOption + DurationOption, + DiagnosticPortOption }; reportCommand.SetAction((parseResult, ct) => Report(ct, @@ -197,7 +228,8 @@ public static Command ReportCommand() stdError: parseResult.Configuration.Error, processId: parseResult.GetValue(ProcessIdOption), name: parseResult.GetValue(NameOption), - duration: parseResult.GetValue(DurationOption))); + duration: parseResult.GetValue(DurationOption), + diagnosticPort: parseResult.GetValue(DiagnosticPortOption))); return reportCommand; } @@ -221,5 +253,11 @@ public static Command ReportCommand() { Description = "The name of the process to report the stack." }; + + private static readonly Option DiagnosticPortOption = + new("--diagnostic-port", "--dport") + { + Description = "The path to a diagnostic port to be used." + }; } } From 1c97f1194daf030609dad62a8e168bf5ecb992d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:38:05 +0000 Subject: [PATCH 03/11] Add test for --diagnostic-port option registration Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com> --- src/tests/dotnet-stack/StackTests.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/tests/dotnet-stack/StackTests.cs b/src/tests/dotnet-stack/StackTests.cs index c689e5a5ba..cff55e2fc8 100644 --- a/src/tests/dotnet-stack/StackTests.cs +++ b/src/tests/dotnet-stack/StackTests.cs @@ -100,5 +100,26 @@ public async Task ReportsStacksCorrectly(TestConfiguration config) Assert.True(correctStackParts[j] == stackParts[i], $"{correctStackParts[j]} != {stackParts[i]}"); } } + + [Fact] + public void DiagnosticPortOptionIsRegistered() + { + Command reportCommand = ReportCommandHandler.ReportCommand(); + + // Verify the diagnostic port option is registered + bool hasDiagnosticPortOption = false; + foreach (var option in reportCommand.Options) + { + if (option.Name == "diagnostic-port") + { + hasDiagnosticPortOption = true; + // Verify it has the short alias --dport + Assert.Contains("--dport", option.Aliases); + break; + } + } + + Assert.True(hasDiagnosticPortOption, "The --diagnostic-port option should be registered in the report command"); + } } } From 07c8f6cb36fef336c1fb94830f3fc8af4ecade66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 22:04:46 +0000 Subject: [PATCH 04/11] Add braces around if statements on lines 41-43 Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com> --- src/Tools/dotnet-stack/ReportCommand.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index c1287843c2..0a027d175b 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -38,9 +38,18 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput { // Validate that only one of processId, name, or diagnosticPort is specified int optionCount = 0; - if (processId != 0) optionCount++; - if (!string.IsNullOrEmpty(name)) optionCount++; - if (!string.IsNullOrEmpty(diagnosticPort)) optionCount++; + if (processId != 0) + { + optionCount++; + } + if (!string.IsNullOrEmpty(name)) + { + optionCount++; + } + if (!string.IsNullOrEmpty(diagnosticPort)) + { + optionCount++; + } if (optionCount == 0) { From a72e9210199ee4b432709db45abc78157e500c99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 22:15:37 +0000 Subject: [PATCH 05/11] Add dotnet-stack to InternalsVisibleTo in NETCore.Client Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com> --- .../Microsoft.Diagnostics.NETCore.Client.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj index c006330b90..098fb2813b 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj +++ b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj @@ -41,6 +41,7 @@ + From 877a49d9171be8547c69f7738d11ff15ebad04c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 01:46:42 +0000 Subject: [PATCH 06/11] Use DiagnosticsClientBuilder to support both connect and listen modes Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com> --- src/Tools/dotnet-stack/ReportCommand.cs | 149 +++++++++++------------- 1 file changed, 68 insertions(+), 81 deletions(-) diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index 0a027d175b..c7fd982112 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -78,107 +78,94 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput return -1; } - DiagnosticsClient client; - if (!string.IsNullOrEmpty(diagnosticPort)) + DiagnosticsClientBuilder builder = new("dotnet-stack", 10); + using (DiagnosticsClientHolder holder = await builder.Build(ct, processId, diagnosticPort, showChildIO: false, printLaunchCommand: false).ConfigureAwait(false)) { - try + if (holder == null) { - IpcEndpointConfig diagnosticPortConfig = IpcEndpointConfig.Parse(diagnosticPort); - if (!diagnosticPortConfig.IsConnectConfig) - { - stdError.WriteLine("--diagnostic-port only supports connect mode to a runtime."); - return -1; - } - client = new DiagnosticsClient(diagnosticPortConfig); - } - catch (Exception ex) - { - stdError.WriteLine($"--diagnostic-port argument error: {ex.Message}"); return -1; } - } - else - { - client = new DiagnosticsClient(processId); - } - List providers = new() - { - new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) - }; + DiagnosticsClient client = holder.Client; - // collect a *short* trace with stack samples - // the hidden '--duration' flag can increase the time of this trace in case 10ms - // is too short in a given environment, e.g., resource constrained systems - // N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect - // the symbol data in rundown. - EventPipeSession session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false); - using (session) - using (FileStream fs = File.OpenWrite(tempNetTraceFilename)) - { - Task copyTask = session.EventStream.CopyToAsync(fs, ct); - await Task.Delay(duration, ct).ConfigureAwait(false); - await session.StopAsync(ct).ConfigureAwait(false); + List providers = new() + { + new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) + }; - // check if rundown is taking more than 5 seconds and add comment to report - Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5)); - Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false); - if (completedTask == timeoutTask) + // collect a *short* trace with stack samples + // the hidden '--duration' flag can increase the time of this trace in case 10ms + // is too short in a given environment, e.g., resource constrained systems + // N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect + // the symbol data in rundown. + EventPipeSession session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false); + using (session) + using (FileStream fs = File.OpenWrite(tempNetTraceFilename)) { - stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time"); + Task copyTask = session.EventStream.CopyToAsync(fs, ct); + await Task.Delay(duration, ct).ConfigureAwait(false); + await session.StopAsync(ct).ConfigureAwait(false); + + // check if rundown is taking more than 5 seconds and add comment to report + Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5)); + Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false); + if (completedTask == timeoutTask) + { + stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time"); + } + await copyTask.ConfigureAwait(false); } - await copyTask.ConfigureAwait(false); - } - // using the generated trace file, symbolicate and compute stacks. - tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(tempNetTraceFilename); - using (SymbolReader symbolReader = new(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }) - using (TraceLog eventLog = new(tempEtlxFilename)) - { - MutableTraceEventStackSource stackSource = new(eventLog) + // using the generated trace file, symbolicate and compute stacks. + tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(tempNetTraceFilename); + using (SymbolReader symbolReader = new(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }) + using (TraceLog eventLog = new(tempEtlxFilename)) { - OnlyManagedCodeStacks = true - }; + MutableTraceEventStackSource stackSource = new(eventLog) + { + OnlyManagedCodeStacks = true + }; - SampleProfilerThreadTimeComputer computer = new(eventLog, symbolReader); - computer.GenerateThreadTimeStacks(stackSource); + SampleProfilerThreadTimeComputer computer = new(eventLog, symbolReader); + computer.GenerateThreadTimeStacks(stackSource); - Dictionary> samplesForThread = new(); + Dictionary> samplesForThread = new(); - stackSource.ForEach((sample) => { - StackSourceCallStackIndex stackIndex = sample.StackIndex; - while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith("Thread (")) - { - stackIndex = stackSource.GetCallerIndex(stackIndex); - } + stackSource.ForEach((sample) => { + StackSourceCallStackIndex stackIndex = sample.StackIndex; + while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith("Thread (")) + { + stackIndex = stackSource.GetCallerIndex(stackIndex); + } - // long form for: int.Parse(threadFrame["Thread (".Length..^1)]) - // Thread id is in the frame name as "Thread ()" - string template = "Thread ("; - string threadFrame = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false); + // long form for: int.Parse(threadFrame["Thread (".Length..^1)]) + // Thread id is in the frame name as "Thread ()" + string template = "Thread ("; + string threadFrame = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false); - // we are looking for the first index of ) because - // we need to handle a thread name like: Thread (4008) (.NET IO ThreadPool Worker) - int firstIndex = threadFrame.IndexOf(')'); - int threadId = int.Parse(threadFrame.AsSpan(template.Length, firstIndex - template.Length)); + // we are looking for the first index of ) because + // we need to handle a thread name like: Thread (4008) (.NET IO ThreadPool Worker) + int firstIndex = threadFrame.IndexOf(')'); + int threadId = int.Parse(threadFrame.AsSpan(template.Length, firstIndex - template.Length)); - if (samplesForThread.TryGetValue(threadId, out List samples)) - { - samples.Add(sample); - } - else - { - samplesForThread[threadId] = new List() { sample }; - } - }); + if (samplesForThread.TryGetValue(threadId, out List samples)) + { + samples.Add(sample); + } + else + { + samplesForThread[threadId] = new List() { sample }; + } + }); - // For every thread recorded in our trace, print the first stack - foreach ((int threadId, List samples) in samplesForThread) - { + // For every thread recorded in our trace, print the first stack + foreach ((int threadId, List samples) in samplesForThread) + { #if DEBUG - stdOutput.WriteLine($"Found {samples.Count} stacks for thread 0x{threadId:X}"); + stdOutput.WriteLine($"Found {samples.Count} stacks for thread 0x{threadId:X}"); #endif - PrintStack(stdOutput, threadId, samples[0], stackSource); + PrintStack(stdOutput, threadId, samples[0], stackSource); + } } } } From ea77b815c313cef1471b6c286b31b45413aad05d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 02:20:39 +0000 Subject: [PATCH 07/11] Include ReversedServerHelpers.cs in dotnet-stack.csproj Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com> --- src/Tools/dotnet-stack/dotnet-stack.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index beacb323f4..2c98d70f15 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -23,6 +23,7 @@ + From 87df662f7d474dd2049b03894973410ac80f8912 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:50:03 +0000 Subject: [PATCH 08/11] Add CommandLineErrorException.cs to dotnet-stack.csproj Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com> --- src/Tools/dotnet-stack/dotnet-stack.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index 2c98d70f15..9f0448817a 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -26,6 +26,7 @@ + From 3a3a765f7a616548d374fca754b856499efd77a3 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Wed, 15 Oct 2025 15:19:22 -0400 Subject: [PATCH 09/11] Added a resume command and a little more clear error message --- src/Tools/dotnet-stack/ReportCommand.cs | 59 +++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index c7fd982112..a1c329bc78 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -88,6 +88,18 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput DiagnosticsClient client = holder.Client; + // Resume runtime if it was suspended (similar to --resume-runtime:true in other tools) + // This is safe to call even if the runtime wasn't suspended - it's a no-op in that case + try + { + await client.ResumeRuntimeAsync(ct).ConfigureAwait(false); + } + catch (Exception ex) + { + // ResumeRuntime is a no-op if the runtime wasn't suspended, + // so we can safely ignore exceptions here + } + List providers = new() { new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) @@ -98,22 +110,40 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput // is too short in a given environment, e.g., resource constrained systems // N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect // the symbol data in rundown. - EventPipeSession session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false); - using (session) - using (FileStream fs = File.OpenWrite(tempNetTraceFilename)) + EventPipeSession session; + try + { + session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false); + } + catch (Exception) + { + stdError.WriteLine(EventPipeErrorMessage); + return -1; + } + + try { - Task copyTask = session.EventStream.CopyToAsync(fs, ct); - await Task.Delay(duration, ct).ConfigureAwait(false); - await session.StopAsync(ct).ConfigureAwait(false); - - // check if rundown is taking more than 5 seconds and add comment to report - Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5)); - Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false); - if (completedTask == timeoutTask) + using (session) + using (FileStream fs = File.OpenWrite(tempNetTraceFilename)) { - stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time"); + Task copyTask = session.EventStream.CopyToAsync(fs, ct); + await Task.Delay(duration, ct).ConfigureAwait(false); + await session.StopAsync(ct).ConfigureAwait(false); + + // check if rundown is taking more than 5 seconds and add comment to report + Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5)); + Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false); + if (completedTask == timeoutTask) + { + stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time"); + } + await copyTask.ConfigureAwait(false); } - await copyTask.ConfigureAwait(false); + } + catch (Exception) + { + stdError.WriteLine(EventPipeErrorMessage); + return -1; } // using the generated trace file, symbolicate and compute stacks. @@ -255,5 +285,8 @@ public static Command ReportCommand() { Description = "The path to a diagnostic port to be used." }; + + private const string EventPipeErrorMessage = + "There was a failure in reading stack data. Possible reasons could be trying to connect to a runtime that has been suspended, an unexpected close of the IPC channel, etc."; } } From 7e8ef6b4f56e576bf852835a71040497369dacf0 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Wed, 15 Oct 2025 15:35:26 -0400 Subject: [PATCH 10/11] Silly me --- src/Tools/dotnet-stack/ReportCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index a1c329bc78..79389c4009 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -94,7 +94,7 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput { await client.ResumeRuntimeAsync(ct).ConfigureAwait(false); } - catch (Exception ex) + catch (Exception) { // ResumeRuntime is a no-op if the runtime wasn't suspended, // so we can safely ignore exceptions here From 79364ff7dad7125c95119aac23c09a6896ad0f92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 23:47:40 +0000 Subject: [PATCH 11/11] Follow dotnet-trace pattern for exception handling - Changed ResumeRuntimeAsync to only catch UnsupportedCommandException per dotnet-trace pattern - Removed overly broad try-catch blocks around EventPipeSession operations - Let exceptions bubble up with full stack traces for better diagnostics - Removed unused EventPipeErrorMessage constant Co-authored-by: noahfalk <6243776+noahfalk@users.noreply.github.com> --- src/Tools/dotnet-stack/ReportCommand.cs | 53 +++++++------------------ 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index 79389c4009..daaa91bb89 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -89,15 +89,13 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput DiagnosticsClient client = holder.Client; // Resume runtime if it was suspended (similar to --resume-runtime:true in other tools) - // This is safe to call even if the runtime wasn't suspended - it's a no-op in that case try { await client.ResumeRuntimeAsync(ct).ConfigureAwait(false); } - catch (Exception) + catch (UnsupportedCommandException) { - // ResumeRuntime is a no-op if the runtime wasn't suspended, - // so we can safely ignore exceptions here + // Noop if command is unsupported, since the target is most likely a 3.1 app. } List providers = new() @@ -110,40 +108,22 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput // is too short in a given environment, e.g., resource constrained systems // N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect // the symbol data in rundown. - EventPipeSession session; - try - { - session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false); - } - catch (Exception) - { - stdError.WriteLine(EventPipeErrorMessage); - return -1; - } - - try + EventPipeSession session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false); + using (session) + using (FileStream fs = File.OpenWrite(tempNetTraceFilename)) { - using (session) - using (FileStream fs = File.OpenWrite(tempNetTraceFilename)) + Task copyTask = session.EventStream.CopyToAsync(fs, ct); + await Task.Delay(duration, ct).ConfigureAwait(false); + await session.StopAsync(ct).ConfigureAwait(false); + + // check if rundown is taking more than 5 seconds and add comment to report + Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5)); + Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false); + if (completedTask == timeoutTask) { - Task copyTask = session.EventStream.CopyToAsync(fs, ct); - await Task.Delay(duration, ct).ConfigureAwait(false); - await session.StopAsync(ct).ConfigureAwait(false); - - // check if rundown is taking more than 5 seconds and add comment to report - Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5)); - Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false); - if (completedTask == timeoutTask) - { - stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time"); - } - await copyTask.ConfigureAwait(false); + stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time"); } - } - catch (Exception) - { - stdError.WriteLine(EventPipeErrorMessage); - return -1; + await copyTask.ConfigureAwait(false); } // using the generated trace file, symbolicate and compute stacks. @@ -285,8 +265,5 @@ public static Command ReportCommand() { Description = "The path to a diagnostic port to be used." }; - - private const string EventPipeErrorMessage = - "There was a failure in reading stack data. Possible reasons could be trying to connect to a runtime that has been suspended, an unexpected close of the IPC channel, etc."; } }