From 155b9af18fd5d6f1482ddde704bce4437bd33d89 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Tue, 28 Oct 2025 22:37:30 +0000 Subject: [PATCH 01/20] [dotnet-counters] Revert CounterMonitor to using IConsole Previously, CounterMonitor had been using IConsole from System.CommandLine instead of the in-house IConsole. As a result, during work to bump to a System.CommandLine version where IConsole was deprecated, CounterMonitor switched to writing to specified TextWriters. Although that decision was fine, this change switches to the in-house IConsole to prepare for redirecting CommandUtils console output to facilitate output validation in tooling tests. --- src/Tools/dotnet-counters/CounterMonitor.cs | 22 +++++++++---------- src/Tools/dotnet-counters/Program.cs | 4 ++-- .../CounterMonitorPayloadTests.cs | 2 +- .../dotnet-counters/CounterMonitorTests.cs | 12 +++++----- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index 9235c65a26..ecf5faed05 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -25,8 +25,7 @@ internal class CounterMonitor : ICountersLogger private const int BufferDelaySecs = 1; private const string EventCountersProviderPrefix = "EventCounters\\"; private int _processId; - private TextWriter _stdOutput; - private TextWriter _stdError; + private IConsole _console; private List _counterList; private ICounterRenderer _renderer; private string _output; @@ -43,12 +42,11 @@ private class ProviderEventState private readonly Dictionary _providerEventStates = new(); private readonly Queue _bufferedEvents = new(); - public CounterMonitor(TextWriter stdOutput, TextWriter stdError) + public CounterMonitor(IConsole console = null) { _pauseCmdSet = false; _shouldExit = new TaskCompletionSource(); - _stdOutput = stdOutput; - _stdError = stdError; + _console = console ?? new DefaultConsole(false); } private void MeterInstrumentEventObserved(string meterName, DateTime timestamp) @@ -238,14 +236,14 @@ public async Task Monitor( { //Cancellation token should automatically stop the session - _stdOutput.WriteLine($"Complete"); + _console.Out.WriteLine($"Complete"); return ReturnCode.Ok; } } } catch (CommandLineErrorException e) { - _stdError.WriteLine(e.Message); + _console.Error.WriteLine(e.Message); return ReturnCode.ArgumentError; } finally @@ -306,7 +304,7 @@ public async Task Collect( _diagnosticsClient = holder.Client; if (_output.Length == 0) { - _stdError.WriteLine("Output cannot be an empty string"); + _console.Error.WriteLine("Output cannot be an empty string"); return ReturnCode.ArgumentError; } if (format == CountersExportFormat.csv) @@ -330,7 +328,7 @@ public async Task Collect( } else { - _stdError.WriteLine($"The output format {format} is not a valid output format."); + _console.Error.WriteLine($"The output format {format} is not a valid output format."); return ReturnCode.ArgumentError; } @@ -352,7 +350,7 @@ public async Task Collect( } catch (CommandLineErrorException e) { - _stdError.WriteLine(e.Message); + _console.Error.WriteLine(e.Message); return ReturnCode.ArgumentError; } finally @@ -388,7 +386,7 @@ internal List ConfigureCounters(string commaSeparatedProv if (counters.Count == 0) { - _stdOutput.WriteLine($"--counters is unspecified. Monitoring System.Runtime counters by default."); + _console.Out.WriteLine($"--counters is unspecified. Monitoring System.Runtime counters by default."); ParseCounterProvider("System.Runtime", counters); } return counters; @@ -537,7 +535,7 @@ private async Task Start(MetricsPipeline pipeline, CancellationToken } catch (DiagnosticsClientException ex) { - Console.WriteLine($"Failed to start the counter session: {ex}"); + _console.WriteLine($"Failed to start the counter session: {ex}"); } catch (Exception ex) { diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs index 2be10faea4..e108fa18c9 100644 --- a/src/Tools/dotnet-counters/Program.cs +++ b/src/Tools/dotnet-counters/Program.cs @@ -39,7 +39,7 @@ private static Command MonitorCommand() monitorCommand.TreatUnmatchedTokensAsErrors = false; // see the logic in Main - monitorCommand.SetAction(static (parseResult, ct) => new CounterMonitor(parseResult.Configuration.Output, parseResult.Configuration.Error).Monitor( + monitorCommand.SetAction(static (parseResult, ct) => new CounterMonitor().Monitor( ct, counters: parseResult.GetValue(CounterOption), processId: parseResult.GetValue(ProcessIdOption), @@ -79,7 +79,7 @@ private static Command CollectCommand() collectCommand.TreatUnmatchedTokensAsErrors = false; // see the logic in Main - collectCommand.SetAction((parseResult, ct) => new CounterMonitor(parseResult.Configuration.Output, parseResult.Configuration.Error).Collect( + collectCommand.SetAction((parseResult, ct) => new CounterMonitor().Collect( ct, counters: parseResult.GetValue(CounterOption), processId: parseResult.GetValue(ProcessIdOption), diff --git a/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs b/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs index f1f6b554c0..38bc614706 100644 --- a/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs +++ b/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs @@ -196,7 +196,7 @@ private async Task> GetCounterTrace(TestConfiguration con { try { - CounterMonitor monitor = new CounterMonitor(TextWriter.Null, TextWriter.Null); + CounterMonitor monitor = new CounterMonitor(); using CancellationTokenSource source = new CancellationTokenSource(DefaultTimeout); diff --git a/src/tests/dotnet-counters/CounterMonitorTests.cs b/src/tests/dotnet-counters/CounterMonitorTests.cs index 30e7bbb816..e090dedb7d 100644 --- a/src/tests/dotnet-counters/CounterMonitorTests.cs +++ b/src/tests/dotnet-counters/CounterMonitorTests.cs @@ -79,7 +79,7 @@ public void GenerateCounterListTestManyProvidersWithFilter() [Fact] public void GenerateCounterListWithOptionAndArgumentsTest() { - CounterMonitor monitor = new(TextWriter.Null, TextWriter.Null); + CounterMonitor monitor = new(); string countersOptionText = "MyEventSource1,MyEventSource2"; List counters = monitor.ConfigureCounters(countersOptionText); Assert.Contains("MyEventSource1", counters.Select(g => g.ProviderName)); @@ -89,7 +89,7 @@ public void GenerateCounterListWithOptionAndArgumentsTest() [Fact] public void ParseErrorUnbalancedBracketsInCountersArg() { - CounterMonitor monitor = new(TextWriter.Null, TextWriter.Null); + CounterMonitor monitor = new(); string countersOptionText = "System.Runtime[cpu-usage,MyEventSource"; CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Expected to find closing ']' in counter_provider", e.Message); @@ -98,7 +98,7 @@ public void ParseErrorUnbalancedBracketsInCountersArg() [Fact] public void ParseErrorTrailingTextInCountersArg() { - CounterMonitor monitor = new(TextWriter.Null, TextWriter.Null); + CounterMonitor monitor = new(); string countersOptionText = "System.Runtime[cpu-usage]hello,MyEventSource"; CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Unexpected characters after closing ']' in counter_provider", e.Message); @@ -107,7 +107,7 @@ public void ParseErrorTrailingTextInCountersArg() [Fact] public void ParseErrorEmptyProvider() { - CounterMonitor monitor = new(TextWriter.Null, TextWriter.Null); + CounterMonitor monitor = new(); string countersOptionText = ",MyEventSource"; CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Expected non-empty counter_provider", e.Message); @@ -116,7 +116,7 @@ public void ParseErrorEmptyProvider() [Fact] public void ParseErrorMultipleCounterLists() { - CounterMonitor monitor = new(TextWriter.Null, TextWriter.Null); + CounterMonitor monitor = new(); string countersOptionText = "System.Runtime[cpu-usage][working-set],MyEventSource"; CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Expected at most one '[' in counter_provider", e.Message); @@ -125,7 +125,7 @@ public void ParseErrorMultipleCounterLists() [Fact] public void ParseErrorMultiplePrefixesOnSameProvider() { - CounterMonitor monitor = new(TextWriter.Null, TextWriter.Null); + CounterMonitor monitor = new(); string countersOptionText = "System.Runtime,MyEventSource,EventCounters\\System.Runtime"; CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Using the same provider name with and without the EventCounters\\ prefix in the counter list is not supported.", e.Message); From 079b98ac3afbca7bed33c70dd504590105c627cc Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Tue, 28 Oct 2025 23:19:18 +0000 Subject: [PATCH 02/20] [dotnet-trace][collect] Set console through constructor --- src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs | 4 ++-- src/tests/dotnet-trace/CollectCommandFunctionalTests.cs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index c9d4124f78..dc395fad0a 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -23,9 +23,9 @@ internal class CollectCommandHandler { internal bool IsQuiet { get; set; } - public CollectCommandHandler() + public CollectCommandHandler(IConsole console = null) { - Console = new DefaultConsole(false); + Console = console ?? new DefaultConsole(false); StartTraceSessionAsync = async (client, config, ct) => new CollectSession(await client.StartEventPipeSessionAsync(config, ct).ConfigureAwait(false)); ResumeRuntimeAsync = (client, ct) => client.ResumeRuntimeAsync(ct); CollectSessionEventStream = (name) => new FileStream(name, FileMode.Create, FileAccess.Write); diff --git a/src/tests/dotnet-trace/CollectCommandFunctionalTests.cs b/src/tests/dotnet-trace/CollectCommandFunctionalTests.cs index ec6d537f3f..e525392a09 100644 --- a/src/tests/dotnet-trace/CollectCommandFunctionalTests.cs +++ b/src/tests/dotnet-trace/CollectCommandFunctionalTests.cs @@ -72,11 +72,10 @@ public async Task CollectCommandInvalidProviderConfiguration_Throws(CollectArgs private static async Task RunAsync(CollectArgs config, MockConsole console) { - var handler = new CollectCommandHandler(); + var handler = new CollectCommandHandler(console); handler.StartTraceSessionAsync = (client, cfg, ct) => Task.FromResult(new TestCollectSession()); handler.ResumeRuntimeAsync = (client, ct) => Task.CompletedTask; handler.CollectSessionEventStream = (name) => config.EventStream; - handler.Console = console; return await handler.Collect( config.ct, From 3422ff62c1c7fb6f0b02d2944473ad6246a6c2c5 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 00:08:44 +0000 Subject: [PATCH 03/20] [CommandUtils] Enable output redirection --- src/Tools/Common/Commands/Utils.cs | 21 ++++++++++++------- src/Tools/Common/DefaultConsole.cs | 2 +- src/Tools/dotnet-counters/CounterMonitor.cs | 6 ++++-- src/Tools/dotnet-dump/Dumper.cs | 4 +++- src/Tools/dotnet-dump/dotnet-dump.csproj | 1 + .../CommandLine/CollectCommandHandler.cs | 4 +++- .../CommandLine/ReportCommandHandler.cs | 4 +++- src/Tools/dotnet-gcdump/dotnet-gcdump.csproj | 1 + src/Tools/dotnet-stack/ReportCommand.cs | 4 +++- src/Tools/dotnet-stack/dotnet-stack.csproj | 1 + .../CommandLine/Commands/CollectCommand.cs | 6 ++++-- 11 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index e6b303c49d..e8f20fc532 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -9,13 +9,20 @@ namespace Microsoft.Internal.Common.Utils { - internal static class CommandUtils + internal sealed class CommandUtils { + private IConsole Console { get; } + + public CommandUtils(IConsole console) + { + Console = console; + } + // Returns processId that matches the given name. // It also checks whether the process has a diagnostics server port. // If there are more than 1 process with the given name or there isn't any active process // with the given name, then this returns -1 - public static int FindProcessIdWithName(string name) + public int FindProcessIdWithName(string name) { List publishedProcessesPids = new(DiagnosticsClient.GetPublishedProcesses()); Process[] processesWithMatchingName = Process.GetProcessesByName(name); @@ -27,7 +34,7 @@ public static int FindProcessIdWithName(string name) { if (commonId != -1) { - Console.WriteLine("There are more than one active processes with the given name: {0}", name); + Console.WriteLine($"There are more than one active processes with the given name: {name}"); return -1; } commonId = processesWithMatchingName[i].Id; @@ -35,7 +42,7 @@ public static int FindProcessIdWithName(string name) } if (commonId == -1) { - Console.WriteLine("There is no active process with the given name: {0}", name); + Console.WriteLine($"There is no active process with the given name: {name}"); } return commonId; } @@ -45,7 +52,7 @@ public static int FindProcessIdWithName(string name) // // dsrouterCommand // processId - public static int LaunchDSRouterProcess(string dsrouterCommand) + public int LaunchDSRouterProcess(string dsrouterCommand) { Console.WriteLine("For finer control over the dotnet-dsrouter options, run it separately and connect to it using -p" + Environment.NewLine); @@ -61,7 +68,7 @@ public static int LaunchDSRouterProcess(string dsrouterCommand) /// name /// port /// - public static bool ValidateArgumentsForChildProcess(int processId, string name, string port) + public bool ValidateArgumentsForChildProcess(int processId, string name, string port) { if (processId != 0 || name != null || !string.IsNullOrEmpty(port)) { @@ -83,7 +90,7 @@ public static bool ValidateArgumentsForChildProcess(int processId, string name, /// dsrouter /// resolvedProcessId /// - public static bool ResolveProcessForAttach(int processId, string name, string port, string dsrouter, out int resolvedProcessId) + public bool ResolveProcessForAttach(int processId, string name, string port, string dsrouter, out int resolvedProcessId) { resolvedProcessId = -1; if (processId == 0 && string.IsNullOrEmpty(name) && string.IsNullOrEmpty(port) && string.IsNullOrEmpty(dsrouter)) diff --git a/src/Tools/Common/DefaultConsole.cs b/src/Tools/Common/DefaultConsole.cs index 974c80a21c..03e3785eb4 100644 --- a/src/Tools/Common/DefaultConsole.cs +++ b/src/Tools/Common/DefaultConsole.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.Tools.Common /// maybe we could map it to System.CommandLine's IConsole, but right now that interface doesn't /// have enough functionality for everything we need. /// - internal class DefaultConsole : IConsole + internal sealed class DefaultConsole : IConsole { private readonly bool _useAnsi; public DefaultConsole(bool useAnsi) diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index ecf5faed05..106f845902 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -33,6 +33,7 @@ internal class CounterMonitor : ICountersLogger private readonly TaskCompletionSource _shouldExit; private DiagnosticsClient _diagnosticsClient; private MetricsPipelineSettings _settings; + private CommandUtils _commandUtils; private class ProviderEventState { @@ -47,6 +48,7 @@ public CounterMonitor(IConsole console = null) _pauseCmdSet = false; _shouldExit = new TaskCompletionSource(); _console = console ?? new DefaultConsole(false); + _commandUtils = new(_console); } private void MeterInstrumentEventObserved(string meterName, DateTime timestamp) @@ -185,7 +187,7 @@ public async Task Monitor( // to it. ValidateNonNegative(maxHistograms, nameof(maxHistograms)); ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries)); - if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) + if (!ProcessLauncher.Launcher.HasChildProc && !_commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) { return ReturnCode.ArgumentError; } @@ -274,7 +276,7 @@ public async Task Collect( // to it. ValidateNonNegative(maxHistograms, nameof(maxHistograms)); ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries)); - if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) + if (!ProcessLauncher.Launcher.HasChildProc && !_commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) { return ReturnCode.ArgumentError; } diff --git a/src/Tools/dotnet-dump/Dumper.cs b/src/Tools/dotnet-dump/Dumper.cs index 65c30d32f3..c79ab77cd7 100644 --- a/src/Tools/dotnet-dump/Dumper.cs +++ b/src/Tools/dotnet-dump/Dumper.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.InteropServices; using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.Diagnostics.Tools.Common; using Microsoft.Internal.Common.Utils; namespace Microsoft.Diagnostics.Tools.Dump @@ -35,7 +36,8 @@ public int Collect(TextWriter stdOutput, TextWriter stdError, int processId, str { try { - if (CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, string.Empty, out int resolvedProcessId)) + CommandUtils commandUtils = new(new DefaultConsole(false)); + if (commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, string.Empty, out int resolvedProcessId)) { processId = resolvedProcessId; } diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj index 991a0fadcf..e630a1306e 100644 --- a/src/Tools/dotnet-dump/dotnet-dump.csproj +++ b/src/Tools/dotnet-dump/dotnet-dump.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index 26af23d866..0cbb1bed67 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Graphs; using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.Diagnostics.Tools.Common; using Microsoft.Internal.Common; using Microsoft.Internal.Common.Utils; @@ -30,7 +31,8 @@ internal static class CollectCommandHandler /// private static async Task Collect(CancellationToken ct, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, string dsrouter) { - if (!CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) + CommandUtils commandUtils = new(new DefaultConsole(false)); + if (!commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) { return -1; } diff --git a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs index c0bab3af39..366eae2c1e 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.Diagnostics.Tools.Common; using Microsoft.Diagnostics.Tools.GCDump.CommandLine; using Microsoft.Internal.Common; using Microsoft.Internal.Common.Utils; @@ -91,7 +92,8 @@ private static Task HandleUnknownParam() private static Task ReportFromProcess(int processId, string diagnosticPort, string dsrouter, CancellationToken ct) { - if (!CommandUtils.ResolveProcessForAttach(processId, string.Empty, diagnosticPort, dsrouter, out int resolvedProcessId)) + CommandUtils commandUtils = new(new DefaultConsole(false)); + if (!commandUtils.ResolveProcessForAttach(processId, string.Empty, diagnosticPort, dsrouter, out int resolvedProcessId)) { return Task.FromResult(-1); } diff --git a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj index 4586ecfc20..748e18f68b 100644 --- a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj +++ b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index c8f2ecc365..800673ee76 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Diagnostics.Symbols; +using Microsoft.Diagnostics.Tools.Common; using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Etlx; using Microsoft.Diagnostics.Tracing.Stacks; @@ -43,7 +44,8 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput Console.WriteLine("Can only specify either --name or --process-id option."); return -1; } - processId = CommandUtils.FindProcessIdWithName(name); + CommandUtils commandUtils = new(new DefaultConsole(false)); + processId = commandUtils.FindProcessIdWithName(name); if (processId < 0) { return -1; diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index beacb323f4..0270ed5abb 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index dc395fad0a..d786f6b4e5 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -22,6 +22,7 @@ namespace Microsoft.Diagnostics.Tools.Trace internal class CollectCommandHandler { internal bool IsQuiet { get; set; } + internal CommandUtils commandUtils; public CollectCommandHandler(IConsole console = null) { @@ -29,6 +30,7 @@ public CollectCommandHandler(IConsole console = null) StartTraceSessionAsync = async (client, config, ct) => new CollectSession(await client.StartEventPipeSessionAsync(config, ct).ConfigureAwait(false)); ResumeRuntimeAsync = (client, ct) => client.ResumeRuntimeAsync(ct); CollectSessionEventStream = (name) => new FileStream(name, FileMode.Create, FileAccess.Write); + commandUtils = new CommandUtils(Console); } private void ConsoleWriteLine(string str = "") @@ -103,7 +105,7 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration Console.WriteLine("--show-child-io must not be specified when attaching to a process"); return (int)ReturnCode.ArgumentError; } - if (CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) + if (commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) { processId = resolvedProcessId; } @@ -112,7 +114,7 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration return (int)ReturnCode.ArgumentError; } } - else if (!CommandUtils.ValidateArgumentsForChildProcess(processId, name, diagnosticPort)) + else if (!commandUtils.ValidateArgumentsForChildProcess(processId, name, diagnosticPort)) { return (int)ReturnCode.ArgumentError; } From 30d433d231550b383e212dae4334cf3bce6d5293 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 02:34:00 +0000 Subject: [PATCH 04/20] Revert "[CommandUtils] Enable output redirection" This reverts commit 3422ff62c1c7fb6f0b02d2944473ad6246a6c2c5. --- src/Tools/Common/Commands/Utils.cs | 21 +++++++------------ src/Tools/Common/DefaultConsole.cs | 2 +- src/Tools/dotnet-counters/CounterMonitor.cs | 6 ++---- src/Tools/dotnet-dump/Dumper.cs | 4 +--- src/Tools/dotnet-dump/dotnet-dump.csproj | 1 - .../CommandLine/CollectCommandHandler.cs | 4 +--- .../CommandLine/ReportCommandHandler.cs | 4 +--- src/Tools/dotnet-gcdump/dotnet-gcdump.csproj | 1 - src/Tools/dotnet-stack/ReportCommand.cs | 4 +--- src/Tools/dotnet-stack/dotnet-stack.csproj | 1 - .../CommandLine/Commands/CollectCommand.cs | 6 ++---- 11 files changed, 16 insertions(+), 38 deletions(-) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index e8f20fc532..e6b303c49d 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -9,20 +9,13 @@ namespace Microsoft.Internal.Common.Utils { - internal sealed class CommandUtils + internal static class CommandUtils { - private IConsole Console { get; } - - public CommandUtils(IConsole console) - { - Console = console; - } - // Returns processId that matches the given name. // It also checks whether the process has a diagnostics server port. // If there are more than 1 process with the given name or there isn't any active process // with the given name, then this returns -1 - public int FindProcessIdWithName(string name) + public static int FindProcessIdWithName(string name) { List publishedProcessesPids = new(DiagnosticsClient.GetPublishedProcesses()); Process[] processesWithMatchingName = Process.GetProcessesByName(name); @@ -34,7 +27,7 @@ public int FindProcessIdWithName(string name) { if (commonId != -1) { - Console.WriteLine($"There are more than one active processes with the given name: {name}"); + Console.WriteLine("There are more than one active processes with the given name: {0}", name); return -1; } commonId = processesWithMatchingName[i].Id; @@ -42,7 +35,7 @@ public int FindProcessIdWithName(string name) } if (commonId == -1) { - Console.WriteLine($"There is no active process with the given name: {name}"); + Console.WriteLine("There is no active process with the given name: {0}", name); } return commonId; } @@ -52,7 +45,7 @@ public int FindProcessIdWithName(string name) // // dsrouterCommand // processId - public int LaunchDSRouterProcess(string dsrouterCommand) + public static int LaunchDSRouterProcess(string dsrouterCommand) { Console.WriteLine("For finer control over the dotnet-dsrouter options, run it separately and connect to it using -p" + Environment.NewLine); @@ -68,7 +61,7 @@ public int LaunchDSRouterProcess(string dsrouterCommand) /// name /// port /// - public bool ValidateArgumentsForChildProcess(int processId, string name, string port) + public static bool ValidateArgumentsForChildProcess(int processId, string name, string port) { if (processId != 0 || name != null || !string.IsNullOrEmpty(port)) { @@ -90,7 +83,7 @@ public bool ValidateArgumentsForChildProcess(int processId, string name, string /// dsrouter /// resolvedProcessId /// - public bool ResolveProcessForAttach(int processId, string name, string port, string dsrouter, out int resolvedProcessId) + public static bool ResolveProcessForAttach(int processId, string name, string port, string dsrouter, out int resolvedProcessId) { resolvedProcessId = -1; if (processId == 0 && string.IsNullOrEmpty(name) && string.IsNullOrEmpty(port) && string.IsNullOrEmpty(dsrouter)) diff --git a/src/Tools/Common/DefaultConsole.cs b/src/Tools/Common/DefaultConsole.cs index 03e3785eb4..974c80a21c 100644 --- a/src/Tools/Common/DefaultConsole.cs +++ b/src/Tools/Common/DefaultConsole.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.Tools.Common /// maybe we could map it to System.CommandLine's IConsole, but right now that interface doesn't /// have enough functionality for everything we need. /// - internal sealed class DefaultConsole : IConsole + internal class DefaultConsole : IConsole { private readonly bool _useAnsi; public DefaultConsole(bool useAnsi) diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index 106f845902..ecf5faed05 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -33,7 +33,6 @@ internal class CounterMonitor : ICountersLogger private readonly TaskCompletionSource _shouldExit; private DiagnosticsClient _diagnosticsClient; private MetricsPipelineSettings _settings; - private CommandUtils _commandUtils; private class ProviderEventState { @@ -48,7 +47,6 @@ public CounterMonitor(IConsole console = null) _pauseCmdSet = false; _shouldExit = new TaskCompletionSource(); _console = console ?? new DefaultConsole(false); - _commandUtils = new(_console); } private void MeterInstrumentEventObserved(string meterName, DateTime timestamp) @@ -187,7 +185,7 @@ public async Task Monitor( // to it. ValidateNonNegative(maxHistograms, nameof(maxHistograms)); ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries)); - if (!ProcessLauncher.Launcher.HasChildProc && !_commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) + if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) { return ReturnCode.ArgumentError; } @@ -276,7 +274,7 @@ public async Task Collect( // to it. ValidateNonNegative(maxHistograms, nameof(maxHistograms)); ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries)); - if (!ProcessLauncher.Launcher.HasChildProc && !_commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) + if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) { return ReturnCode.ArgumentError; } diff --git a/src/Tools/dotnet-dump/Dumper.cs b/src/Tools/dotnet-dump/Dumper.cs index c79ab77cd7..65c30d32f3 100644 --- a/src/Tools/dotnet-dump/Dumper.cs +++ b/src/Tools/dotnet-dump/Dumper.cs @@ -6,7 +6,6 @@ using System.IO; using System.Runtime.InteropServices; using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Diagnostics.Tools.Common; using Microsoft.Internal.Common.Utils; namespace Microsoft.Diagnostics.Tools.Dump @@ -36,8 +35,7 @@ public int Collect(TextWriter stdOutput, TextWriter stdError, int processId, str { try { - CommandUtils commandUtils = new(new DefaultConsole(false)); - if (commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, string.Empty, out int resolvedProcessId)) + if (CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, string.Empty, out int resolvedProcessId)) { processId = resolvedProcessId; } diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj index e630a1306e..991a0fadcf 100644 --- a/src/Tools/dotnet-dump/dotnet-dump.csproj +++ b/src/Tools/dotnet-dump/dotnet-dump.csproj @@ -24,7 +24,6 @@ - diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index 0cbb1bed67..26af23d866 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Graphs; using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Diagnostics.Tools.Common; using Microsoft.Internal.Common; using Microsoft.Internal.Common.Utils; @@ -31,8 +30,7 @@ internal static class CollectCommandHandler /// private static async Task Collect(CancellationToken ct, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, string dsrouter) { - CommandUtils commandUtils = new(new DefaultConsole(false)); - if (!commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) + if (!CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) { return -1; } diff --git a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs index 366eae2c1e..c0bab3af39 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Diagnostics.Tools.Common; using Microsoft.Diagnostics.Tools.GCDump.CommandLine; using Microsoft.Internal.Common; using Microsoft.Internal.Common.Utils; @@ -92,8 +91,7 @@ private static Task HandleUnknownParam() private static Task ReportFromProcess(int processId, string diagnosticPort, string dsrouter, CancellationToken ct) { - CommandUtils commandUtils = new(new DefaultConsole(false)); - if (!commandUtils.ResolveProcessForAttach(processId, string.Empty, diagnosticPort, dsrouter, out int resolvedProcessId)) + if (!CommandUtils.ResolveProcessForAttach(processId, string.Empty, diagnosticPort, dsrouter, out int resolvedProcessId)) { return Task.FromResult(-1); } diff --git a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj index 748e18f68b..4586ecfc20 100644 --- a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj +++ b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj @@ -28,7 +28,6 @@ - diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index 800673ee76..c8f2ecc365 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Diagnostics.Symbols; -using Microsoft.Diagnostics.Tools.Common; using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Etlx; using Microsoft.Diagnostics.Tracing.Stacks; @@ -44,8 +43,7 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput Console.WriteLine("Can only specify either --name or --process-id option."); return -1; } - CommandUtils commandUtils = new(new DefaultConsole(false)); - processId = commandUtils.FindProcessIdWithName(name); + processId = CommandUtils.FindProcessIdWithName(name); if (processId < 0) { return -1; diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index 0270ed5abb..beacb323f4 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -27,7 +27,6 @@ - diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index d786f6b4e5..dc395fad0a 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -22,7 +22,6 @@ namespace Microsoft.Diagnostics.Tools.Trace internal class CollectCommandHandler { internal bool IsQuiet { get; set; } - internal CommandUtils commandUtils; public CollectCommandHandler(IConsole console = null) { @@ -30,7 +29,6 @@ public CollectCommandHandler(IConsole console = null) StartTraceSessionAsync = async (client, config, ct) => new CollectSession(await client.StartEventPipeSessionAsync(config, ct).ConfigureAwait(false)); ResumeRuntimeAsync = (client, ct) => client.ResumeRuntimeAsync(ct); CollectSessionEventStream = (name) => new FileStream(name, FileMode.Create, FileAccess.Write); - commandUtils = new CommandUtils(Console); } private void ConsoleWriteLine(string str = "") @@ -105,7 +103,7 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration Console.WriteLine("--show-child-io must not be specified when attaching to a process"); return (int)ReturnCode.ArgumentError; } - if (commandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) + if (CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) { processId = resolvedProcessId; } @@ -114,7 +112,7 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration return (int)ReturnCode.ArgumentError; } } - else if (!commandUtils.ValidateArgumentsForChildProcess(processId, name, diagnosticPort)) + else if (!CommandUtils.ValidateArgumentsForChildProcess(processId, name, diagnosticPort)) { return (int)ReturnCode.ArgumentError; } From b116e70d0873d5637d6e072cd0727fafdcd2ea88 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 03:06:54 +0000 Subject: [PATCH 05/20] [CommandUtils] Convert FindProcessIdWithName to throw CommandLineErrorException --- src/Tools/Common/Commands/Utils.cs | 10 +++------- src/Tools/dotnet-stack/ReportCommand.cs | 10 +++++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index e6b303c49d..04274c43e1 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -27,15 +27,14 @@ public static int FindProcessIdWithName(string name) { if (commonId != -1) { - Console.WriteLine("There are more than one active processes with the given name: {0}", name); - return -1; + throw new CommandLineErrorException($"There are more than one active processes with the given name: {name}"); } commonId = processesWithMatchingName[i].Id; } } if (commonId == -1) { - Console.WriteLine("There is no active process with the given name: {0}", name); + throw new CommandLineErrorException($"There is no active process with the given name: {name}"); } return commonId; } @@ -113,10 +112,7 @@ public static bool ResolveProcessForAttach(int processId, string name, string po // Resolve name option else if (!string.IsNullOrEmpty(name)) { - if ((processId = FindProcessIdWithName(name)) < 0) - { - return false; - } + processId = FindProcessIdWithName(name); } else if (!string.IsNullOrEmpty(dsrouter)) { diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index c8f2ecc365..5d59398e60 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -44,10 +44,6 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput return -1; } processId = CommandUtils.FindProcessIdWithName(name); - if (processId < 0) - { - return -1; - } } if (processId < 0) @@ -61,7 +57,6 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput return -1; } - DiagnosticsClient client = new(processId); List providers = new() { @@ -143,6 +138,11 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput } } } + catch (CommandLineErrorException e) + { + stdError.WriteLine($"[ERROR] {e.Message}"); + return -1; + } catch (OperationCanceledException) { return -1; From 44f7364932e8a8ab9382c8170add7c4e1ff483c7 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 03:07:49 +0000 Subject: [PATCH 06/20] [CommandUtils] Convert ValidateArgumentsForChildProcess to throw --- src/Tools/Common/Commands/Utils.cs | 7 ++----- .../dotnet-trace/CommandLine/Commands/CollectCommand.cs | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index 04274c43e1..47019ba46e 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -60,15 +60,12 @@ public static int LaunchDSRouterProcess(string dsrouterCommand) /// name /// port /// - public static bool ValidateArgumentsForChildProcess(int processId, string name, string port) + public static void ValidateArgumentsForChildProcess(int processId, string name, string port) { if (processId != 0 || name != null || !string.IsNullOrEmpty(port)) { - Console.WriteLine("None of the --name, --process-id, or --diagnostic-port options may be specified when launching a child process."); - return false; + throw new CommandLineErrorException("None of the --name, --process-id, or --diagnostic-port options may be specified when launching a child process."); } - - return true; } /// diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index dc395fad0a..ebf5329a5b 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -112,9 +112,9 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration return (int)ReturnCode.ArgumentError; } } - else if (!CommandUtils.ValidateArgumentsForChildProcess(processId, name, diagnosticPort)) + else { - return (int)ReturnCode.ArgumentError; + CommandUtils.ValidateArgumentsForChildProcess(processId, name, diagnosticPort); } if (profile.Length == 0 && providers.Length == 0 && clrevents.Length == 0) From 7a8450a2fdd22e4a065d85d2ff3ade6394596353 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 03:26:36 +0000 Subject: [PATCH 07/20] [CommandUtils] Convert ResolveProcessForAttach to throw --- src/Tools/Common/Commands/Utils.cs | 24 +++++-------- src/Tools/dotnet-counters/CounterMonitor.cs | 8 ++--- src/Tools/dotnet-dump/Dumper.cs | 15 ++++---- .../CommandLine/CollectCommandHandler.cs | 34 +++++++++--------- .../CommandLine/ReportCommandHandler.cs | 35 +++++++++++-------- .../CommandLine/Commands/CollectCommand.cs | 10 ++---- 6 files changed, 58 insertions(+), 68 deletions(-) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index 47019ba46e..89660dc715 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -79,18 +79,16 @@ public static void ValidateArgumentsForChildProcess(int processId, string name, /// dsrouter /// resolvedProcessId /// - public static bool ResolveProcessForAttach(int processId, string name, string port, string dsrouter, out int resolvedProcessId) + public static void ResolveProcessForAttach(int processId, string name, string port, string dsrouter, out int resolvedProcessId) { resolvedProcessId = -1; if (processId == 0 && string.IsNullOrEmpty(name) && string.IsNullOrEmpty(port) && string.IsNullOrEmpty(dsrouter)) { - Console.WriteLine("Must specify either --process-id, --name, --diagnostic-port, or --dsrouter."); - return false; + throw new CommandLineErrorException("Must specify either --process-id, --name, --diagnostic-port, or --dsrouter."); } else if (processId < 0) { - Console.WriteLine($"{processId} is not a valid process ID"); - return false; + throw new CommandLineErrorException($"{processId} is not a valid process ID"); } else if ((processId != 0 ? 1 : 0) + (!string.IsNullOrEmpty(name) ? 1 : 0) + @@ -98,13 +96,12 @@ public static bool ResolveProcessForAttach(int processId, string name, string po (!string.IsNullOrEmpty(dsrouter) ? 1 : 0) != 1) { - Console.WriteLine("Only one of the --name, --process-id, --diagnostic-port, or --dsrouter options may be specified."); - return false; + throw new CommandLineErrorException("Only one of the --name, --process-id, --diagnostic-port, or --dsrouter options may be specified."); } // If we got this far it means only one of --name/--diagnostic-port/--process-id/--dsrouter was specified else if (!string.IsNullOrEmpty(port)) { - return true; + return; } // Resolve name option else if (!string.IsNullOrEmpty(name)) @@ -115,25 +112,22 @@ public static bool ResolveProcessForAttach(int processId, string name, string po { if (dsrouter != "ios" && dsrouter != "android" && dsrouter != "ios-sim" && dsrouter != "android-emu") { - Console.WriteLine("Invalid value for --dsrouter. Valid values are 'ios', 'ios-sim', 'android' and 'android-emu'."); - return false; + throw new CommandLineErrorException("Invalid value for --dsrouter. Valid values are 'ios', 'ios-sim', 'android' and 'android-emu'."); } if ((processId = LaunchDSRouterProcess(dsrouter)) < 0) { if (processId == -2) { - Console.WriteLine($"Failed to launch dsrouter: {dsrouter}. Make sure that dotnet-dsrouter is not already running. You can connect to an already running dsrouter with -p."); + throw new CommandLineErrorException($"Failed to launch dsrouter: {dsrouter}. Make sure that dotnet-dsrouter is not already running. You can connect to an already running dsrouter with -p."); } else { - Console.WriteLine($"Failed to launch dsrouter: {dsrouter}. Please make sure that dotnet-dsrouter is installed and available in the same directory as dotnet-trace."); - Console.WriteLine("You can install dotnet-dsrouter by running 'dotnet tool install --global dotnet-dsrouter'. More info at https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter"); + throw new CommandLineErrorException($"Failed to launch dsrouter: {dsrouter}. Please make sure that dotnet-dsrouter is installed and available in the same directory as dotnet-trace.\n" + + "You can install dotnet-dsrouter by running 'dotnet tool install --global dotnet-dsrouter'. More info at https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter"); } - return false; } } resolvedProcessId = processId; - return true; } } diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index ecf5faed05..6febcdd1da 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -185,9 +185,9 @@ public async Task Monitor( // to it. ValidateNonNegative(maxHistograms, nameof(maxHistograms)); ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries)); - if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) + if (!ProcessLauncher.Launcher.HasChildProc) { - return ReturnCode.ArgumentError; + CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId); } ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok)); @@ -274,9 +274,9 @@ public async Task Collect( // to it. ValidateNonNegative(maxHistograms, nameof(maxHistograms)); ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries)); - if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId)) + if (!ProcessLauncher.Launcher.HasChildProc) { - return ReturnCode.ArgumentError; + CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId); } ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok)); diff --git a/src/Tools/dotnet-dump/Dumper.cs b/src/Tools/dotnet-dump/Dumper.cs index 65c30d32f3..d27d788638 100644 --- a/src/Tools/dotnet-dump/Dumper.cs +++ b/src/Tools/dotnet-dump/Dumper.cs @@ -35,14 +35,8 @@ public int Collect(TextWriter stdOutput, TextWriter stdError, int processId, str { try { - if (CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, string.Empty, out int resolvedProcessId)) - { - processId = resolvedProcessId; - } - else - { - return -1; - } + CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, string.Empty, out int resolvedProcessId); + processId = resolvedProcessId; if (output == null) { @@ -132,6 +126,11 @@ public int Collect(TextWriter stdOutput, TextWriter stdError, int processId, str client.WriteDump(dumpType, output, flags); } } + catch (CommandLineErrorException e) + { + stdError.WriteLine($"[ERROR] {e.Message}"); + return -1; + } catch (Exception ex) { if (diag) diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index 26af23d866..b8a8f2b840 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -30,16 +30,12 @@ internal static class CollectCommandHandler /// private static async Task Collect(CancellationToken ct, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, string dsrouter) { - if (!CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) + try { - return -1; - } + CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId); + processId = resolvedProcessId; - processId = resolvedProcessId; - - if (!string.IsNullOrEmpty(diagnosticPort)) - { - try + if (!string.IsNullOrEmpty(diagnosticPort)) { IpcEndpointConfig config = IpcEndpointConfig.Parse(diagnosticPort); if (!config.IsConnectConfig) @@ -47,18 +43,10 @@ private static async Task Collect(CancellationToken ct, int processId, stri Console.Error.WriteLine("--diagnostic-port is only supporting connect mode."); return -1; } - } - catch (Exception ex) - { - Console.Error.WriteLine($"--diagnostic-port argument error: {ex.Message}"); - return -1; - } - processId = 0; - } + processId = 0; + } - try - { output = string.IsNullOrEmpty(output) ? $"{DateTime.Now:yyyyMMdd\\_HHmmss}_{processId}.gcdump" : output; @@ -106,6 +94,16 @@ private static async Task Collect(CancellationToken ct, int processId, stri return -1; } } + catch (CommandLineErrorException e) + { + Console.Error.WriteLine($"[ERROR] {e.Message}"); + return -1; + } + catch (FormatException fe) + { + Console.Error.WriteLine($"--diagnostic-port argument error: {fe.Message}"); + return -1; + } catch (Exception ex) { Console.Error.WriteLine($"[ERROR] {ex}"); diff --git a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs index c0bab3af39..3c728c1266 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs @@ -91,16 +91,12 @@ private static Task HandleUnknownParam() private static Task ReportFromProcess(int processId, string diagnosticPort, string dsrouter, CancellationToken ct) { - if (!CommandUtils.ResolveProcessForAttach(processId, string.Empty, diagnosticPort, dsrouter, out int resolvedProcessId)) + try { - return Task.FromResult(-1); - } + CommandUtils.ResolveProcessForAttach(processId, string.Empty, diagnosticPort, dsrouter, out int resolvedProcessId); + processId = resolvedProcessId; - processId = resolvedProcessId; - - if (!string.IsNullOrEmpty(diagnosticPort)) - { - try + if (!string.IsNullOrEmpty(diagnosticPort)) { IpcEndpointConfig config = IpcEndpointConfig.Parse(diagnosticPort); if (!config.IsConnectConfig) @@ -108,14 +104,23 @@ private static Task ReportFromProcess(int processId, string diagnosticPort, Console.Error.WriteLine("--diagnostic-port is only supporting connect mode."); return Task.FromResult(-1); } + processId = 0; } - catch (Exception ex) - { - Console.Error.WriteLine($"--diagnostic-port argument error: {ex.Message}"); - return Task.FromResult(-1); - } - - processId = 0; + } + catch (CommandLineErrorException e) + { + Console.Error.WriteLine($"[ERROR] {e.Message}"); + return Task.FromResult(-1); + } + catch (FormatException fe) + { + Console.Error.WriteLine($"--diagnostic-port argument error: {fe.Message}"); + return Task.FromResult(-1); + } + catch (Exception ex) + { + Console.Error.WriteLine($"[ERROR] {ex}"); + return Task.FromResult(-1); } if (!CollectCommandHandler diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index ebf5329a5b..50004a8ef1 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -103,14 +103,8 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration Console.WriteLine("--show-child-io must not be specified when attaching to a process"); return (int)ReturnCode.ArgumentError; } - if (CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) - { - processId = resolvedProcessId; - } - else - { - return (int)ReturnCode.ArgumentError; - } + CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId); + processId = resolvedProcessId; } else { From 2e9cab0a52225d5e4d8fc9eb7ae2dedaa7de02a6 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 03:49:40 +0000 Subject: [PATCH 08/20] Add corresponding compile items --- src/Tools/Common/Commands/Utils.cs | 1 + src/Tools/dotnet-dump/dotnet-dump.csproj | 1 + src/Tools/dotnet-gcdump/dotnet-gcdump.csproj | 1 + src/Tools/dotnet-stack/dotnet-stack.csproj | 1 + 4 files changed, 4 insertions(+) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index 89660dc715..f5289fcc88 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Collections.Generic; using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.Diagnostics.Tools; using Microsoft.Diagnostics.Tools.Common; namespace Microsoft.Internal.Common.Utils diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj index 991a0fadcf..f9c030d8ea 100644 --- a/src/Tools/dotnet-dump/dotnet-dump.csproj +++ b/src/Tools/dotnet-dump/dotnet-dump.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj index 4586ecfc20..bf07cfcf0b 100644 --- a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj +++ b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index beacb323f4..1e09ffcad2 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -27,6 +27,7 @@ + From f7f7704aa653324db6a1e31f3113496729e91b3c Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 03:49:54 +0000 Subject: [PATCH 09/20] Fix exception typo --- .../DiagnosticsIpc/IpcEndpointConfig.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcEndpointConfig.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcEndpointConfig.cs index bf63c928b6..77d8429577 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcEndpointConfig.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcEndpointConfig.cs @@ -133,7 +133,7 @@ public static IpcEndpointConfig Parse(string config) string[] parts = config.Split(','); if (parts.Length > 2) { - throw new FormatException($"Unknow IPC endpoint config format, {config}."); + throw new FormatException($"Unknown IPC endpoint config format, {config}."); } if (string.IsNullOrEmpty(parts[0])) @@ -156,7 +156,7 @@ public static IpcEndpointConfig Parse(string config) } else { - throw new FormatException($"Unknow IPC endpoint config keyword, {parts[1]} in {config}."); + throw new FormatException($"Unknown IPC endpoint config keyword, {parts[1]} in {config}."); } } } From 90f78410bfde5e1c3d3d9db14365f3c23535f615 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 03:51:45 +0000 Subject: [PATCH 10/20] Default DefaultConsole to not use ansi --- src/Tools/Common/DefaultConsole.cs | 2 +- src/Tools/dotnet-counters/CounterMonitor.cs | 2 +- src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs | 2 +- .../dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs | 2 +- src/Tools/dotnet-trace/ProviderUtils.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tools/Common/DefaultConsole.cs b/src/Tools/Common/DefaultConsole.cs index 974c80a21c..41e9556d7f 100644 --- a/src/Tools/Common/DefaultConsole.cs +++ b/src/Tools/Common/DefaultConsole.cs @@ -15,7 +15,7 @@ namespace Microsoft.Diagnostics.Tools.Common internal class DefaultConsole : IConsole { private readonly bool _useAnsi; - public DefaultConsole(bool useAnsi) + public DefaultConsole(bool useAnsi = false) { _useAnsi = useAnsi; } diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index 6febcdd1da..ca4d4ef9a8 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -46,7 +46,7 @@ public CounterMonitor(IConsole console = null) { _pauseCmdSet = false; _shouldExit = new TaskCompletionSource(); - _console = console ?? new DefaultConsole(false); + _console = console ?? new DefaultConsole(); } private void MeterInstrumentEventObserved(string meterName, DateTime timestamp) diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index 50004a8ef1..c001861efe 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -25,7 +25,7 @@ internal class CollectCommandHandler public CollectCommandHandler(IConsole console = null) { - Console = console ?? new DefaultConsole(false); + Console = console ?? new DefaultConsole(); StartTraceSessionAsync = async (client, config, ct) => new CollectSession(await client.StartEventPipeSessionAsync(config, ct).ConfigureAwait(false)); ResumeRuntimeAsync = (client, ct) => client.ResumeRuntimeAsync(ct); CollectSessionEventStream = (name) => new FileStream(name, FileMode.Create, FileAccess.Write); diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs index aab6d437a8..585294fe35 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs @@ -35,7 +35,7 @@ internal sealed record CollectLinuxArgs( public CollectLinuxCommandHandler(IConsole console = null) { - Console = console ?? new DefaultConsole(false); + Console = console ?? new DefaultConsole(); rewriter = new LineRewriter(Console); } diff --git a/src/Tools/dotnet-trace/ProviderUtils.cs b/src/Tools/dotnet-trace/ProviderUtils.cs index 0adffe6450..cb980f2073 100644 --- a/src/Tools/dotnet-trace/ProviderUtils.cs +++ b/src/Tools/dotnet-trace/ProviderUtils.cs @@ -76,7 +76,7 @@ private enum ProviderSource public static List ComputeProviderConfig(string[] providersArg, string clreventsArg, string clreventlevel, string[] profiles, bool shouldPrintProviders = false, string verbExclusivity = null, IConsole console = null) { - console ??= new DefaultConsole(false); + console ??= new DefaultConsole(); Dictionary merged = new(StringComparer.OrdinalIgnoreCase); Dictionary providerSources = new(StringComparer.OrdinalIgnoreCase); From 7b0aa798534f715a855b380d5e24ab408752daa3 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 19:31:33 +0000 Subject: [PATCH 11/20] [dotnet-trace] Fix ReturnCode from CommandLineErrorException --- src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs | 2 +- .../dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs | 2 +- src/tests/dotnet-trace/CollectCommandFunctionalTests.cs | 2 +- src/tests/dotnet-trace/CollectLinuxCommandFunctionalTests.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index c001861efe..f29660b21b 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -472,7 +472,7 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration { Console.Error.WriteLine($"[ERROR] {e.Message}"); collectionStopped = true; - ret = (int)ReturnCode.TracingError; + ret = (int)ReturnCode.ArgumentError; } catch (OperationCanceledException) { diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs index 585294fe35..16aebee71f 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs @@ -82,7 +82,7 @@ internal int CollectLinux(CollectLinuxArgs args) catch (CommandLineErrorException e) { Console.Error.WriteLine($"[ERROR] {e.Message}"); - ret = (int)ReturnCode.TracingError; + ret = (int)ReturnCode.ArgumentError; } catch (Exception ex) { diff --git a/src/tests/dotnet-trace/CollectCommandFunctionalTests.cs b/src/tests/dotnet-trace/CollectCommandFunctionalTests.cs index e525392a09..2ca87ace32 100644 --- a/src/tests/dotnet-trace/CollectCommandFunctionalTests.cs +++ b/src/tests/dotnet-trace/CollectCommandFunctionalTests.cs @@ -66,7 +66,7 @@ public async Task CollectCommandInvalidProviderConfiguration_Throws(CollectArgs { MockConsole console = new(200, 30); int exitCode = await RunAsync(args, console).ConfigureAwait(true); - Assert.Equal((int)ReturnCode.TracingError, exitCode); + Assert.Equal((int)ReturnCode.ArgumentError, exitCode); console.AssertSanitizedLinesEqual(CollectSanitizer, expectedException); } diff --git a/src/tests/dotnet-trace/CollectLinuxCommandFunctionalTests.cs b/src/tests/dotnet-trace/CollectLinuxCommandFunctionalTests.cs index 6b1eb3ad33..7cd7daf83e 100644 --- a/src/tests/dotnet-trace/CollectLinuxCommandFunctionalTests.cs +++ b/src/tests/dotnet-trace/CollectLinuxCommandFunctionalTests.cs @@ -66,7 +66,7 @@ public void CollectLinuxCommandProviderConfigurationConsolidation_Throws(object int exitCode = Run(testArgs, console); if (OperatingSystem.IsLinux()) { - Assert.Equal((int)ReturnCode.TracingError, exitCode); + Assert.Equal((int)ReturnCode.ArgumentError, exitCode); console.AssertSanitizedLinesEqual(null, expectedException); } else From 6be25ae0d04f6d4559607199888a8f2f03e592e1 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 21:29:09 +0000 Subject: [PATCH 12/20] [dotnet-trace] Handle DSRouter Launch Failure ReturnCode --- src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index f29660b21b..dd7090a765 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -473,6 +473,10 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration Console.Error.WriteLine($"[ERROR] {e.Message}"); collectionStopped = true; ret = (int)ReturnCode.ArgumentError; + if (e.Message.StartsWith("Failed to launch dsrouter", StringComparison.OrdinalIgnoreCase)) + { + ret = (int)ReturnCode.TracingError; + } } catch (OperationCanceledException) { From 0e29459cc26b347843bc88693213b17652b7ea0c Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 29 Oct 2025 21:37:04 +0000 Subject: [PATCH 13/20] Cleanup ProcessLauncher Start --- .../ReversedServerHelpers/ReversedServerHelpers.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs b/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs index b51f0cb130..f779a2d858 100644 --- a/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs +++ b/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs @@ -89,7 +89,7 @@ public Process ChildProc return _childProc; } } - public bool Start(string diagnosticTransportName, CancellationToken ct, bool showChildIO, bool printLaunchCommand) + public void Start(string diagnosticTransportName, CancellationToken ct, bool showChildIO, bool printLaunchCommand) { _childProc.StartInfo.UseShellExecute = false; _childProc.StartInfo.RedirectStandardOutput = !showChildIO; @@ -114,8 +114,6 @@ public bool Start(string diagnosticTransportName, CancellationToken ct, bool sho _stdOutTask = ReadAndIgnoreAllStreamAsync(_childProc.StandardOutput, ct); _stdErrTask = ReadAndIgnoreAllStreamAsync(_childProc.StandardError, ct); } - - return true; } public void Cleanup() @@ -231,10 +229,7 @@ public async Task Build(CancellationToken ct, int proce server.Start(); // Start the child proc - if (!ProcessLauncher.Launcher.Start(diagnosticTransportName, ct, showChildIO, printLaunchCommand)) - { - throw new InvalidOperationException($"Failed to start '{ProcessLauncher.Launcher.ChildProc.StartInfo.FileName} {ProcessLauncher.Launcher.ChildProc.StartInfo.Arguments}'."); - } + ProcessLauncher.Launcher.Start(diagnosticTransportName, ct, showChildIO, printLaunchCommand); IpcEndpointInfo endpointInfo; try { From e2d336a439f52b4e41b458e4cba6a649885a90c2 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 30 Oct 2025 15:25:27 +0000 Subject: [PATCH 14/20] Rename CommandLineErrorException --- src/Tools/Common/Commands/Utils.cs | 18 +++++++++--------- ...Exception.cs => DiagnosticToolException.cs} | 4 ++-- .../ReversedServerHelpers.cs | 2 +- src/Tools/dotnet-counters/CounterMonitor.cs | 12 ++++++------ .../dotnet-counters/dotnet-counters.csproj | 2 +- .../dotnet-dsrouter/dotnet-dsrouter.csproj | 2 +- src/Tools/dotnet-dump/Dumper.cs | 4 ++-- src/Tools/dotnet-dump/dotnet-dump.csproj | 2 +- .../CommandLine/CollectCommandHandler.cs | 4 ++-- .../CommandLine/ReportCommandHandler.cs | 4 ++-- src/Tools/dotnet-gcdump/dotnet-gcdump.csproj | 2 +- src/Tools/dotnet-stack/ReportCommand.cs | 4 ++-- src/Tools/dotnet-stack/dotnet-stack.csproj | 2 +- .../CommandLine/Commands/CollectCommand.cs | 6 +++--- .../Commands/CollectLinuxCommand.cs | 6 +++--- src/Tools/dotnet-trace/ProviderUtils.cs | 12 ++++++------ .../dotnet-trace/TraceFileFormatConverter.cs | 4 ++-- .../dotnet-counters/CounterMonitorTests.cs | 10 +++++----- src/tests/dotnet-trace/CLRProviderParsing.cs | 4 ++-- .../dotnet-trace/ProviderCompositionTests.cs | 14 +++++++------- 20 files changed, 59 insertions(+), 59 deletions(-) rename src/Tools/Common/{CommandLineErrorException.cs => DiagnosticToolException.cs} (85%) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index f5289fcc88..c38b85a55c 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -28,14 +28,14 @@ public static int FindProcessIdWithName(string name) { if (commonId != -1) { - throw new CommandLineErrorException($"There are more than one active processes with the given name: {name}"); + throw new DiagnosticToolException($"There are more than one active processes with the given name: {name}"); } commonId = processesWithMatchingName[i].Id; } } if (commonId == -1) { - throw new CommandLineErrorException($"There is no active process with the given name: {name}"); + throw new DiagnosticToolException($"There is no active process with the given name: {name}"); } return commonId; } @@ -65,7 +65,7 @@ public static void ValidateArgumentsForChildProcess(int processId, string name, { if (processId != 0 || name != null || !string.IsNullOrEmpty(port)) { - throw new CommandLineErrorException("None of the --name, --process-id, or --diagnostic-port options may be specified when launching a child process."); + throw new DiagnosticToolException("None of the --name, --process-id, or --diagnostic-port options may be specified when launching a child process."); } } @@ -85,11 +85,11 @@ public static void ResolveProcessForAttach(int processId, string name, string po resolvedProcessId = -1; if (processId == 0 && string.IsNullOrEmpty(name) && string.IsNullOrEmpty(port) && string.IsNullOrEmpty(dsrouter)) { - throw new CommandLineErrorException("Must specify either --process-id, --name, --diagnostic-port, or --dsrouter."); + throw new DiagnosticToolException("Must specify either --process-id, --name, --diagnostic-port, or --dsrouter."); } else if (processId < 0) { - throw new CommandLineErrorException($"{processId} is not a valid process ID"); + throw new DiagnosticToolException($"{processId} is not a valid process ID"); } else if ((processId != 0 ? 1 : 0) + (!string.IsNullOrEmpty(name) ? 1 : 0) + @@ -97,7 +97,7 @@ public static void ResolveProcessForAttach(int processId, string name, string po (!string.IsNullOrEmpty(dsrouter) ? 1 : 0) != 1) { - throw new CommandLineErrorException("Only one of the --name, --process-id, --diagnostic-port, or --dsrouter options may be specified."); + throw new DiagnosticToolException("Only one of the --name, --process-id, --diagnostic-port, or --dsrouter options may be specified."); } // If we got this far it means only one of --name/--diagnostic-port/--process-id/--dsrouter was specified else if (!string.IsNullOrEmpty(port)) @@ -113,17 +113,17 @@ public static void ResolveProcessForAttach(int processId, string name, string po { if (dsrouter != "ios" && dsrouter != "android" && dsrouter != "ios-sim" && dsrouter != "android-emu") { - throw new CommandLineErrorException("Invalid value for --dsrouter. Valid values are 'ios', 'ios-sim', 'android' and 'android-emu'."); + throw new DiagnosticToolException("Invalid value for --dsrouter. Valid values are 'ios', 'ios-sim', 'android' and 'android-emu'."); } if ((processId = LaunchDSRouterProcess(dsrouter)) < 0) { if (processId == -2) { - throw new CommandLineErrorException($"Failed to launch dsrouter: {dsrouter}. Make sure that dotnet-dsrouter is not already running. You can connect to an already running dsrouter with -p."); + throw new DiagnosticToolException($"Failed to launch dsrouter: {dsrouter}. Make sure that dotnet-dsrouter is not already running. You can connect to an already running dsrouter with -p."); } else { - throw new CommandLineErrorException($"Failed to launch dsrouter: {dsrouter}. Please make sure that dotnet-dsrouter is installed and available in the same directory as dotnet-trace.\n" + + throw new DiagnosticToolException($"Failed to launch dsrouter: {dsrouter}. Please make sure that dotnet-dsrouter is installed and available in the same directory as dotnet-trace.\n" + "You can install dotnet-dsrouter by running 'dotnet tool install --global dotnet-dsrouter'. More info at https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter"); } } diff --git a/src/Tools/Common/CommandLineErrorException.cs b/src/Tools/Common/DiagnosticToolException.cs similarity index 85% rename from src/Tools/Common/CommandLineErrorException.cs rename to src/Tools/Common/DiagnosticToolException.cs index aa1792e8e9..ccac741814 100644 --- a/src/Tools/Common/CommandLineErrorException.cs +++ b/src/Tools/Common/DiagnosticToolException.cs @@ -16,8 +16,8 @@ namespace Microsoft.Diagnostics.Tools // // For any other error conditions that were unanticipated or do not have // contextualized error messages, don't use this type. - internal sealed class CommandLineErrorException : Exception + internal sealed class DiagnosticToolException : Exception { - public CommandLineErrorException(string errorMessage) : base(errorMessage) { } + public DiagnosticToolException(string errorMessage) : base(errorMessage) { } } } diff --git a/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs b/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs index f779a2d858..79f8af70b5 100644 --- a/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs +++ b/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs @@ -107,7 +107,7 @@ public void Start(string diagnosticTransportName, CancellationToken ct, bool sho } catch (Exception e) { - throw new CommandLineErrorException($"An error occurred trying to start process '{_childProc.StartInfo.FileName}' with working directory '{System.IO.Directory.GetCurrentDirectory()}'. {e.Message}"); + throw new DiagnosticToolException($"An error occurred trying to start process '{_childProc.StartInfo.FileName}' with working directory '{System.IO.Directory.GetCurrentDirectory()}'. {e.Message}"); } if (!showChildIO) { diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index ca4d4ef9a8..0a9c8ce4c6 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -241,9 +241,9 @@ public async Task Monitor( } } } - catch (CommandLineErrorException e) + catch (DiagnosticToolException dte) { - _console.Error.WriteLine(e.Message); + _console.Error.WriteLine(dte.Message); return ReturnCode.ArgumentError; } finally @@ -348,9 +348,9 @@ public async Task Collect( } } } - catch (CommandLineErrorException e) + catch (DiagnosticToolException dte) { - _console.Error.WriteLine(e.Message); + _console.Error.WriteLine(dte.Message); return ReturnCode.ArgumentError; } finally @@ -363,7 +363,7 @@ private static void ValidateNonNegative(int value, string argName) { if (value < 0) { - throw new CommandLineErrorException($"Argument --{argName} must be non-negative"); + throw new DiagnosticToolException($"Argument --{argName} must be non-negative"); } } @@ -381,7 +381,7 @@ internal List ConfigureCounters(string commaSeparatedProv { // the FormatException message strings thrown by ParseProviderList are controlled // by us and anticipate being integrated into the command-line error text. - throw new CommandLineErrorException("Error parsing --counters argument: " + e.Message); + throw new DiagnosticToolException("Error parsing --counters argument: " + e.Message); } if (counters.Count == 0) diff --git a/src/Tools/dotnet-counters/dotnet-counters.csproj b/src/Tools/dotnet-counters/dotnet-counters.csproj index 2ac31e5434..d077b34c1d 100644 --- a/src/Tools/dotnet-counters/dotnet-counters.csproj +++ b/src/Tools/dotnet-counters/dotnet-counters.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj b/src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj index ebed4febb6..c0fd89a5ff 100644 --- a/src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj +++ b/src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Tools/dotnet-dump/Dumper.cs b/src/Tools/dotnet-dump/Dumper.cs index d27d788638..54d12eafd9 100644 --- a/src/Tools/dotnet-dump/Dumper.cs +++ b/src/Tools/dotnet-dump/Dumper.cs @@ -126,9 +126,9 @@ public int Collect(TextWriter stdOutput, TextWriter stdError, int processId, str client.WriteDump(dumpType, output, flags); } } - catch (CommandLineErrorException e) + catch (DiagnosticToolException dte) { - stdError.WriteLine($"[ERROR] {e.Message}"); + stdError.WriteLine($"[ERROR] {dte.Message}"); return -1; } catch (Exception ex) diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj index f9c030d8ea..dcf1e71061 100644 --- a/src/Tools/dotnet-dump/dotnet-dump.csproj +++ b/src/Tools/dotnet-dump/dotnet-dump.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index b8a8f2b840..b5a44b6895 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -94,9 +94,9 @@ private static async Task Collect(CancellationToken ct, int processId, stri return -1; } } - catch (CommandLineErrorException e) + catch (DiagnosticToolException dte) { - Console.Error.WriteLine($"[ERROR] {e.Message}"); + Console.Error.WriteLine($"[ERROR] {dte.Message}"); return -1; } catch (FormatException fe) diff --git a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs index 3c728c1266..092a8c592a 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs @@ -107,9 +107,9 @@ private static Task ReportFromProcess(int processId, string diagnosticPort, processId = 0; } } - catch (CommandLineErrorException e) + catch (DiagnosticToolException dte) { - Console.Error.WriteLine($"[ERROR] {e.Message}"); + Console.Error.WriteLine($"[ERROR] {dte.Message}"); return Task.FromResult(-1); } catch (FormatException fe) diff --git a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj index bf07cfcf0b..b4fe4a026e 100644 --- a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj +++ b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index 5d59398e60..59257233be 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -138,9 +138,9 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput } } } - catch (CommandLineErrorException e) + catch (DiagnosticToolException dte) { - stdError.WriteLine($"[ERROR] {e.Message}"); + stdError.WriteLine($"[ERROR] {dte.Message}"); return -1; } catch (OperationCanceledException) diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index 1e09ffcad2..80797161e4 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index dd7090a765..5f84641e23 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -468,12 +468,12 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration } } } - catch (CommandLineErrorException e) + catch (DiagnosticToolException dte) { - Console.Error.WriteLine($"[ERROR] {e.Message}"); + Console.Error.WriteLine($"[ERROR] {dte.Message}"); collectionStopped = true; ret = (int)ReturnCode.ArgumentError; - if (e.Message.StartsWith("Failed to launch dsrouter", StringComparison.OrdinalIgnoreCase)) + if (dte.Message.StartsWith("Failed to launch dsrouter", StringComparison.OrdinalIgnoreCase)) { ret = (int)ReturnCode.TracingError; } diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs index 16aebee71f..c316ea4a02 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs @@ -79,9 +79,9 @@ internal int CollectLinux(CollectLinuxArgs args) stopwatch.Start(); ret = RecordTraceInvoker(command, (UIntPtr)command.Length, OutputHandler); } - catch (CommandLineErrorException e) + catch (DiagnosticToolException dte) { - Console.Error.WriteLine($"[ERROR] {e.Message}"); + Console.Error.WriteLine($"[ERROR] {dte.Message}"); ret = (int)ReturnCode.ArgumentError; } catch (Exception ex) @@ -197,7 +197,7 @@ private byte[] BuildRecordTraceArgs(CollectLinuxArgs args, out string scriptPath string[] split = perfEvent.Split(':', 2, StringSplitOptions.TrimEntries); if (split.Length != 2 || string.IsNullOrEmpty(split[0]) || string.IsNullOrEmpty(split[1])) { - throw new CommandLineErrorException($"Invalid perf event specification '{perfEvent}'. Expected format 'provider:event'."); + throw new DiagnosticToolException($"Invalid perf event specification '{perfEvent}'. Expected format 'provider:event'."); } string perfProvider = split[0]; diff --git a/src/Tools/dotnet-trace/ProviderUtils.cs b/src/Tools/dotnet-trace/ProviderUtils.cs index cb980f2073..c8dd83312c 100644 --- a/src/Tools/dotnet-trace/ProviderUtils.cs +++ b/src/Tools/dotnet-trace/ProviderUtils.cs @@ -101,14 +101,14 @@ public static List ComputeProviderConfig(string[] providersAr if (traceProfile == null) { - throw new CommandLineErrorException($"Invalid profile name: {profile}"); + throw new DiagnosticToolException($"Invalid profile name: {profile}"); } if (!string.IsNullOrEmpty(verbExclusivity) && !string.IsNullOrEmpty(traceProfile.VerbExclusivity) && !string.Equals(traceProfile.VerbExclusivity, verbExclusivity, StringComparison.OrdinalIgnoreCase)) { - throw new CommandLineErrorException($"The specified profile '{traceProfile.Name}' does not apply to `dotnet-trace {verbExclusivity}`."); + throw new DiagnosticToolException($"The specified profile '{traceProfile.Name}' does not apply to `dotnet-trace {verbExclusivity}`."); } IEnumerable profileProviders = traceProfile.Providers; @@ -158,7 +158,7 @@ private static EventPipeProvider MergeProviderConfigs(EventPipeProvider provider if (providerConfigA.Arguments != null && providerConfigB.Arguments != null) { - throw new CommandLineErrorException($"Provider \"{providerConfigA.Name}\" is declared multiple times with filter arguments."); + throw new DiagnosticToolException($"Provider \"{providerConfigA.Name}\" is declared multiple times with filter arguments."); } return new EventPipeProvider(providerConfigA.Name, level, providerConfigA.Keywords | providerConfigB.Keywords, providerConfigA.Arguments ?? providerConfigB.Arguments); @@ -218,7 +218,7 @@ public static EventPipeProvider ToCLREventPipeProvider(string clreventslist, str } else { - throw new CommandLineErrorException($"{clrevents[i]} is not a valid CLR event keyword"); + throw new DiagnosticToolException($"{clrevents[i]} is not a valid CLR event keyword"); } } @@ -256,7 +256,7 @@ private static EventLevel GetEventLevel(string token) case "warning": return EventLevel.Warning; default: - throw new CommandLineErrorException($"Unknown EventLevel: {token}"); + throw new DiagnosticToolException($"Unknown EventLevel: {token}"); } } } @@ -281,7 +281,7 @@ private static EventPipeProvider ToProvider(string provider, IConsole console) if (string.IsNullOrWhiteSpace(providerName)) { - throw new CommandLineErrorException("Provider name was not specified."); + throw new DiagnosticToolException("Provider name was not specified."); } // Keywords diff --git a/src/Tools/dotnet-trace/TraceFileFormatConverter.cs b/src/Tools/dotnet-trace/TraceFileFormatConverter.cs index 61e4c4c1b8..a6e08ac74c 100644 --- a/src/Tools/dotnet-trace/TraceFileFormatConverter.cs +++ b/src/Tools/dotnet-trace/TraceFileFormatConverter.cs @@ -62,7 +62,7 @@ internal static void ConvertToFormat(TextWriter stdOut, TextWriter stdError, Tra break; default: // Validation happened way before this, so we shoud never reach this... - throw new CommandLineErrorException($"Invalid TraceFileFormat \"{format}\""); + throw new DiagnosticToolException($"Invalid TraceFileFormat \"{format}\""); } stdOut.WriteLine("Conversion complete"); } @@ -94,7 +94,7 @@ private static void Convert(TraceFileFormat format, string fileToConvert, string break; default: // we should never get here - throw new CommandLineErrorException($"Invalid TraceFileFormat \"{format}\""); + throw new DiagnosticToolException($"Invalid TraceFileFormat \"{format}\""); } } diff --git a/src/tests/dotnet-counters/CounterMonitorTests.cs b/src/tests/dotnet-counters/CounterMonitorTests.cs index e090dedb7d..c840d91b83 100644 --- a/src/tests/dotnet-counters/CounterMonitorTests.cs +++ b/src/tests/dotnet-counters/CounterMonitorTests.cs @@ -91,7 +91,7 @@ public void ParseErrorUnbalancedBracketsInCountersArg() { CounterMonitor monitor = new(); string countersOptionText = "System.Runtime[cpu-usage,MyEventSource"; - CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); + DiagnosticToolException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Expected to find closing ']' in counter_provider", e.Message); } @@ -100,7 +100,7 @@ public void ParseErrorTrailingTextInCountersArg() { CounterMonitor monitor = new(); string countersOptionText = "System.Runtime[cpu-usage]hello,MyEventSource"; - CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); + DiagnosticToolException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Unexpected characters after closing ']' in counter_provider", e.Message); } @@ -109,7 +109,7 @@ public void ParseErrorEmptyProvider() { CounterMonitor monitor = new(); string countersOptionText = ",MyEventSource"; - CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); + DiagnosticToolException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Expected non-empty counter_provider", e.Message); } @@ -118,7 +118,7 @@ public void ParseErrorMultipleCounterLists() { CounterMonitor monitor = new(); string countersOptionText = "System.Runtime[cpu-usage][working-set],MyEventSource"; - CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); + DiagnosticToolException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Expected at most one '[' in counter_provider", e.Message); } @@ -127,7 +127,7 @@ public void ParseErrorMultiplePrefixesOnSameProvider() { CounterMonitor monitor = new(); string countersOptionText = "System.Runtime,MyEventSource,EventCounters\\System.Runtime"; - CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); + DiagnosticToolException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText)); Assert.Equal("Error parsing --counters argument: Using the same provider name with and without the EventCounters\\ prefix in the counter list is not supported.", e.Message); } } diff --git a/src/tests/dotnet-trace/CLRProviderParsing.cs b/src/tests/dotnet-trace/CLRProviderParsing.cs index 3b3f80a35e..427a0cfd22 100644 --- a/src/tests/dotnet-trace/CLRProviderParsing.cs +++ b/src/tests/dotnet-trace/CLRProviderParsing.cs @@ -29,7 +29,7 @@ public void ValidSingleCLREvent(string providerToParse) [InlineData("haha")] public void InValidSingleCLREvent(string providerToParse) { - Assert.Throws(() => ProviderUtils.ToCLREventPipeProvider(providerToParse, "4")); + Assert.Throws(() => ProviderUtils.ToCLREventPipeProvider(providerToParse, "4")); } [Theory] @@ -64,7 +64,7 @@ public void ValidCLREventLevel(string clreventlevel) [InlineData("hello")] public void InvalidCLREventLevel(string clreventlevel) { - Assert.Throws(() => ProviderUtils.ToCLREventPipeProvider("gc", clreventlevel)); + Assert.Throws(() => ProviderUtils.ToCLREventPipeProvider("gc", clreventlevel)); } } } diff --git a/src/tests/dotnet-trace/ProviderCompositionTests.cs b/src/tests/dotnet-trace/ProviderCompositionTests.cs index d7441fbe6c..c5a9a8c766 100644 --- a/src/tests/dotnet-trace/ProviderCompositionTests.cs +++ b/src/tests/dotnet-trace/ProviderCompositionTests.cs @@ -43,10 +43,10 @@ public static IEnumerable ValidProviders() public static IEnumerable InvalidProviders() { - yield return new object[] { ":::", typeof(CommandLineErrorException) }; - yield return new object[] { ":1:1", typeof(CommandLineErrorException) }; - yield return new object[] { "ProviderOne:0x1:UnknownLevel", typeof(CommandLineErrorException) }; - yield return new object[] { "VeryCoolProvider:0x0:-1", typeof(CommandLineErrorException) }; + yield return new object[] { ":::", typeof(DiagnosticToolException) }; + yield return new object[] { ":1:1", typeof(DiagnosticToolException) }; + yield return new object[] { "ProviderOne:0x1:UnknownLevel", typeof(DiagnosticToolException) }; + yield return new object[] { "VeryCoolProvider:0x0:-1", typeof(DiagnosticToolException) }; yield return new object[] { "VeryCoolProvider:0xFFFFFFFFFFFFFFFFF:5:FilterAndPayloadSpecs=\"QuotedValue\"", typeof(OverflowException) }; yield return new object[] { "VeryCoolProvider:0x10000000000000000::FilterAndPayloadSpecs=\"QuotedValue\"", typeof(OverflowException) }; yield return new object[] { "VeryCoolProvider:__:5:FilterAndPayloadSpecs=\"QuotedValue\"", typeof(FormatException) }; @@ -120,7 +120,7 @@ public void MultipleProviders_Parse_AsExpected(string providersArg, EventPipePro public static IEnumerable MultipleInvalidProviders() { - yield return new object[] { "ProviderOne:0x1:5:FilterAndPayloadSpecs=\"QuotedValue\",:2:2:key=value,ProviderThree:3:3:key=value", typeof(CommandLineErrorException) }; + yield return new object[] { "ProviderOne:0x1:5:FilterAndPayloadSpecs=\"QuotedValue\",:2:2:key=value,ProviderThree:3:3:key=value", typeof(DiagnosticToolException) }; yield return new object[] { "ProviderOne:0x1:5:FilterAndPayloadSpecs=\"QuotedValue\",ProviderTwo:0xFFFFFFFFFFFFFFFFF:5:FilterAndPayloadSpecs=\"QuotedValue\",ProviderThree:3:3:key=value", typeof(OverflowException) }; yield return new object[] { "ProviderOne:0x1:5:FilterAndPayloadSpecs=\"QuotedValue\",ProviderTwo:0x10000000000000000:5:FilterAndPayloadSpecs=\"QuotedValue\",ProviderThree:3:3:key=value", typeof(OverflowException) }; yield return new object[] { "ProviderOne:0x1:5:FilterAndPayloadSpecs=\"QuotedValue\",ProviderTwo:18446744073709551615:5:FilterAndPayloadSpecs=\"QuotedValue\",ProviderThree:3:3:key=value", typeof(OverflowException) }; @@ -146,7 +146,7 @@ public static IEnumerable DedupeSuccessCases() public static IEnumerable DedupeFailureCases() { - yield return new object[] { new[]{ "MyProvider:::key=value", "MyProvider:::key=value" }, typeof(CommandLineErrorException) }; + yield return new object[] { new[]{ "MyProvider:::key=value", "MyProvider:::key=value" }, typeof(DiagnosticToolException) }; } [Theory] @@ -233,7 +233,7 @@ public void ProviderSourcePrecedence(string[] providersArg, string clreventsArg, public static IEnumerable InvalidClrEvents() { - yield return new object[] { Array.Empty(), "gc+bogus", string.Empty, Array.Empty(), typeof(CommandLineErrorException) }; + yield return new object[] { Array.Empty(), "gc+bogus", string.Empty, Array.Empty(), typeof(DiagnosticToolException) }; } [Theory] From 2814b51225eac7fce92b046b05203050255f7f80 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Fri, 31 Oct 2025 15:55:29 +0000 Subject: [PATCH 15/20] [CommandUtils] Convert ResolveProcess to throw --- src/Tools/Common/Commands/Utils.cs | 20 ++++++---------- src/Tools/dotnet-stack/ReportCommand.cs | 23 ++----------------- .../Commands/CollectLinuxCommand.cs | 17 ++++++-------- 3 files changed, 16 insertions(+), 44 deletions(-) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index 4a8c7488ab..5a80179b6f 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -72,31 +72,28 @@ public static void ValidateArgumentsForChildProcess(int processId, string name, /// /// A helper method for validating --process-id, --name options for collect commands and resolving the process ID and name. /// Only one of these options can be specified, so it checks for duplicate options specified and if there is - /// such duplication, it prints the appropriate error message. + /// such duplication, it throws the appropriate DiagnosticToolException error message. /// /// process ID /// name /// resolvedProcessId /// resolvedProcessName /// - public static bool ResolveProcess(int processId, string name, out int resolvedProcessId, out string resolvedProcessName) + public static void ResolveProcess(int processId, string name, out int resolvedProcessId, out string resolvedProcessName) { resolvedProcessId = -1; resolvedProcessName = name; if (processId == 0 && string.IsNullOrEmpty(name)) { - Console.Error.WriteLine("Must specify either --process-id or --name."); - return false; + throw new DiagnosticToolException("Must specify either --process-id or --name."); } else if (processId < 0) { - Console.Error.WriteLine($"{processId} is not a valid process ID"); - return false; + throw new DiagnosticToolException($"{processId} is not a valid process ID"); } else if ((processId != 0) && !string.IsNullOrEmpty(name)) { - Console.Error.WriteLine("Only one of the --name or --process-id options may be specified."); - return false; + throw new DiagnosticToolException("Only one of the --name or --process-id options may be specified."); } try { @@ -113,17 +110,14 @@ public static bool ResolveProcess(int processId, string name, out int resolvedPr } catch (ArgumentException) { - Console.Error.WriteLine($"No process with ID {processId} is currently running."); - return false; + throw new DiagnosticToolException($"No process with ID {processId} is currently running."); } - - return resolvedProcessId != -1; } /// /// A helper method for validating --process-id, --name, --diagnostic-port, --dsrouter options for collect commands and resolving the process ID. /// Only one of these options can be specified, so it checks for duplicate options specified and if there is - /// such duplication, it prints the appropriate error message. + /// such duplication, it throws the appropriate DiagnosticToolException error message. /// /// process ID /// name diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs index 59257233be..21e73f2d7e 100644 --- a/src/Tools/dotnet-stack/ReportCommand.cs +++ b/src/Tools/dotnet-stack/ReportCommand.cs @@ -35,27 +35,8 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput try { - // Either processName or processId has to be specified. - 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) - { - stdError.WriteLine("Process ID should not be negative."); - return -1; - } - else if (processId == 0) - { - stdError.WriteLine("--process-id is required"); - return -1; - } + CommandUtils.ResolveProcess(processId, name, out int resolvedProcessId, out string _); + processId = resolvedProcessId; DiagnosticsClient client = new(processId); List providers = new() diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs index 285b725cd7..cbb32ae11b 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs @@ -53,15 +53,6 @@ internal int CollectLinux(CollectLinuxArgs args) return (int)ReturnCode.PlatformNotSupportedError; } - if (args.ProcessId != 0 || !string.IsNullOrEmpty(args.Name)) - { - if (!CommandUtils.ResolveProcess(args.ProcessId, args.Name, out int resolvedProcessId, out string resolvedProcessName)) - { - return (int)ReturnCode.ArgumentError; - } - args = args with { Name = resolvedProcessName, ProcessId = resolvedProcessId }; - } - Console.WriteLine("=========================================================================================="); Console.WriteLine("The collect-linux verb is a new preview feature and relies on an updated version of the"); Console.WriteLine(".nettrace file format. The latest PerfView release supports these trace files but other"); @@ -69,11 +60,17 @@ internal int CollectLinux(CollectLinuxArgs args) Console.WriteLine("https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-trace."); Console.WriteLine("=========================================================================================="); - args.Ct.Register(() => stopTracing = true); int ret = (int)ReturnCode.TracingError; string scriptPath = null; try { + if (args.ProcessId != 0 || !string.IsNullOrEmpty(args.Name)) + { + CommandUtils.ResolveProcess(args.ProcessId, args.Name, out int resolvedProcessId, out string resolvedProcessName); + args = args with { Name = resolvedProcessName, ProcessId = resolvedProcessId }; + } + + args.Ct.Register(() => stopTracing = true); Console.CursorVisible = false; byte[] command = BuildRecordTraceArgs(args, out scriptPath); From bdeb602b256b9e04dc20df9dc769d63ea7b2c511 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Tue, 4 Nov 2025 22:32:29 +0000 Subject: [PATCH 16/20] Add ReturnCode to DiagnosticToolException --- src/Tools/Common/Commands/Utils.cs | 4 ++-- src/Tools/Common/DiagnosticToolException.cs | 6 +++++- src/Tools/dotnet-counters/CounterMonitor.cs | 4 ++-- .../dotnet-trace/CommandLine/Commands/CollectCommand.cs | 6 +----- .../CommandLine/Commands/CollectLinuxCommand.cs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index 5a80179b6f..0276499d35 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -164,12 +164,12 @@ public static void ResolveProcessForAttach(int processId, string name, string po { if (processId == -2) { - throw new DiagnosticToolException($"Failed to launch dsrouter: {dsrouter}. Make sure that dotnet-dsrouter is not already running. You can connect to an already running dsrouter with -p."); + throw new DiagnosticToolException($"Failed to launch dsrouter: {dsrouter}. Make sure that dotnet-dsrouter is not already running. You can connect to an already running dsrouter with -p.", (int)ReturnCode.TracingError); } else { throw new DiagnosticToolException($"Failed to launch dsrouter: {dsrouter}. Please make sure that dotnet-dsrouter is installed and available in the same directory as dotnet-trace.\n" + - "You can install dotnet-dsrouter by running 'dotnet tool install --global dotnet-dsrouter'. More info at https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter"); + "You can install dotnet-dsrouter by running 'dotnet tool install --global dotnet-dsrouter'. More info at https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter", (int)ReturnCode.TracingError); } } } diff --git a/src/Tools/Common/DiagnosticToolException.cs b/src/Tools/Common/DiagnosticToolException.cs index ccac741814..c91166d658 100644 --- a/src/Tools/Common/DiagnosticToolException.cs +++ b/src/Tools/Common/DiagnosticToolException.cs @@ -18,6 +18,10 @@ namespace Microsoft.Diagnostics.Tools // contextualized error messages, don't use this type. internal sealed class DiagnosticToolException : Exception { - public DiagnosticToolException(string errorMessage) : base(errorMessage) { } + public int ReturnCode { get; } + public DiagnosticToolException(string errorMessage, int returnCode = 3 /* ReturnCode.ArgumentError */) : base(errorMessage) + { + ReturnCode = returnCode; + } } } diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index 0a9c8ce4c6..ff694c0b08 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -244,7 +244,7 @@ public async Task Monitor( catch (DiagnosticToolException dte) { _console.Error.WriteLine(dte.Message); - return ReturnCode.ArgumentError; + return (ReturnCode)dte.ReturnCode; } finally { @@ -351,7 +351,7 @@ public async Task Collect( catch (DiagnosticToolException dte) { _console.Error.WriteLine(dte.Message); - return ReturnCode.ArgumentError; + return (ReturnCode)dte.ReturnCode; } finally { diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index 5f84641e23..8fadb1e966 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -472,11 +472,7 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration { Console.Error.WriteLine($"[ERROR] {dte.Message}"); collectionStopped = true; - ret = (int)ReturnCode.ArgumentError; - if (dte.Message.StartsWith("Failed to launch dsrouter", StringComparison.OrdinalIgnoreCase)) - { - ret = (int)ReturnCode.TracingError; - } + ret = dte.ReturnCode; } catch (OperationCanceledException) { diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs index cbb32ae11b..2fc24dcafe 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs @@ -90,7 +90,7 @@ internal int CollectLinux(CollectLinuxArgs args) catch (DiagnosticToolException dte) { Console.Error.WriteLine($"[ERROR] {dte.Message}"); - ret = (int)ReturnCode.ArgumentError; + ret = dte.ReturnCode; } catch (Exception ex) { From cced4592eaf844113fa3264228e769aab20f9570 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 5 Nov 2025 16:53:36 +0000 Subject: [PATCH 17/20] [ToolsCommon] Break out ReturnCode Before, if any file wanted to use ReturnCode, it's project would need to include all dependencies of Utils.cs, regardless of usage. By moving ReturnCode into it's own source, we break the dependency. --- src/Tools/Common/Commands/Utils.cs | 14 ++------------ src/Tools/Common/DiagnosticToolException.cs | 5 +++-- src/Tools/Common/ReturnCode.cs | 15 +++++++++++++++ src/Tools/dotnet-counters/dotnet-counters.csproj | 1 + src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj | 1 + src/Tools/dotnet-dump/dotnet-dump.csproj | 1 + src/Tools/dotnet-gcdump/dotnet-gcdump.csproj | 1 + src/Tools/dotnet-stack/dotnet-stack.csproj | 1 + .../CommandLine/Commands/CollectCommand.cs | 2 +- .../CommandLine/Commands/CollectLinuxCommand.cs | 2 +- 10 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 src/Tools/Common/ReturnCode.cs diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index 0276499d35..eaa3da8205 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -164,12 +164,12 @@ public static void ResolveProcessForAttach(int processId, string name, string po { if (processId == -2) { - throw new DiagnosticToolException($"Failed to launch dsrouter: {dsrouter}. Make sure that dotnet-dsrouter is not already running. You can connect to an already running dsrouter with -p.", (int)ReturnCode.TracingError); + throw new DiagnosticToolException($"Failed to launch dsrouter: {dsrouter}. Make sure that dotnet-dsrouter is not already running. You can connect to an already running dsrouter with -p.", ReturnCode.TracingError); } else { throw new DiagnosticToolException($"Failed to launch dsrouter: {dsrouter}. Please make sure that dotnet-dsrouter is installed and available in the same directory as dotnet-trace.\n" + - "You can install dotnet-dsrouter by running 'dotnet tool install --global dotnet-dsrouter'. More info at https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter", (int)ReturnCode.TracingError); + "You can install dotnet-dsrouter by running 'dotnet tool install --global dotnet-dsrouter'. More info at https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter", ReturnCode.TracingError); } } } @@ -243,14 +243,4 @@ bool EnsureInitialized() } } } - - internal enum ReturnCode - { - Ok, - SessionCreationError, - TracingError, - ArgumentError, - PlatformNotSupportedError, - UnknownError - } } diff --git a/src/Tools/Common/DiagnosticToolException.cs b/src/Tools/Common/DiagnosticToolException.cs index c91166d658..224582347f 100644 --- a/src/Tools/Common/DiagnosticToolException.cs +++ b/src/Tools/Common/DiagnosticToolException.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Internal.Common.Utils; namespace Microsoft.Diagnostics.Tools { @@ -18,8 +19,8 @@ namespace Microsoft.Diagnostics.Tools // contextualized error messages, don't use this type. internal sealed class DiagnosticToolException : Exception { - public int ReturnCode { get; } - public DiagnosticToolException(string errorMessage, int returnCode = 3 /* ReturnCode.ArgumentError */) : base(errorMessage) + public ReturnCode ReturnCode { get; } + public DiagnosticToolException(string errorMessage, ReturnCode returnCode = ReturnCode.ArgumentError ) : base(errorMessage) { ReturnCode = returnCode; } diff --git a/src/Tools/Common/ReturnCode.cs b/src/Tools/Common/ReturnCode.cs new file mode 100644 index 0000000000..8b3e01e955 --- /dev/null +++ b/src/Tools/Common/ReturnCode.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Internal.Common.Utils +{ + internal enum ReturnCode + { + Ok, + SessionCreationError, + TracingError, + ArgumentError, + PlatformNotSupportedError, + UnknownError + } +} diff --git a/src/Tools/dotnet-counters/dotnet-counters.csproj b/src/Tools/dotnet-counters/dotnet-counters.csproj index d077b34c1d..727fe4183f 100644 --- a/src/Tools/dotnet-counters/dotnet-counters.csproj +++ b/src/Tools/dotnet-counters/dotnet-counters.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj b/src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj index c0fd89a5ff..db3fdb5d81 100644 --- a/src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj +++ b/src/Tools/dotnet-dsrouter/dotnet-dsrouter.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj index dcf1e71061..512a63cd8a 100644 --- a/src/Tools/dotnet-dump/dotnet-dump.csproj +++ b/src/Tools/dotnet-dump/dotnet-dump.csproj @@ -25,6 +25,7 @@ + diff --git a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj index b4fe4a026e..28181718c9 100644 --- a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj +++ b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj @@ -29,6 +29,7 @@ + diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index 80797161e4..dc21ad0d86 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index 8fadb1e966..9c6ec43d63 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -472,7 +472,7 @@ internal async Task Collect(CancellationToken ct, CommandLineConfiguration { Console.Error.WriteLine($"[ERROR] {dte.Message}"); collectionStopped = true; - ret = dte.ReturnCode; + ret = (int)dte.ReturnCode; } catch (OperationCanceledException) { diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs index 2fc24dcafe..a7798c27ab 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs @@ -90,7 +90,7 @@ internal int CollectLinux(CollectLinuxArgs args) catch (DiagnosticToolException dte) { Console.Error.WriteLine($"[ERROR] {dte.Message}"); - ret = dte.ReturnCode; + ret = (int)dte.ReturnCode; } catch (Exception ex) { From 56d74515e83c526fb7dec669647fb9af61477418 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 5 Nov 2025 16:57:55 +0000 Subject: [PATCH 18/20] [ToolsCommon] Break out LineRewriter LineRewriter depends on IConsole, so whenever Tools used CommandUtils, they would need that dependency regardless of whether they actually used IConsole. Like ReturnCode, moving LineRewriter breaks unnecessary dependencies. --- src/Tools/Common/Commands/Utils.cs | 68 ----------------- src/Tools/Common/LineRewriter.cs | 74 +++++++++++++++++++ .../dotnet-counters/dotnet-counters.csproj | 2 +- src/Tools/dotnet-dump/dotnet-dump.csproj | 1 - src/Tools/dotnet-gcdump/dotnet-gcdump.csproj | 1 - src/Tools/dotnet-stack/dotnet-stack.csproj | 1 - 6 files changed, 75 insertions(+), 72 deletions(-) create mode 100644 src/Tools/Common/LineRewriter.cs diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index eaa3da8205..e0f39a808b 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Diagnostics.Tools; -using Microsoft.Diagnostics.Tools.Common; namespace Microsoft.Internal.Common.Utils { @@ -176,71 +175,4 @@ public static void ResolveProcessForAttach(int processId, string name, string po resolvedProcessId = processId; } } - - internal sealed class LineRewriter - { - public int LineToClear { get; set; } - - private IConsole Console { get; } - - public LineRewriter(IConsole console) - { - Console = console; - } - - // ANSI escape codes: - // [2K => clear current line - // [{LineToClear};0H => move cursor to column 0 of row `LineToClear` - public void RewriteConsoleLine() - { - bool useConsoleFallback = true; - if (!Console.IsInputRedirected) - { - // in case of console input redirection, the control ANSI codes would appear - - // first attempt ANSI Codes - int before = Console.CursorTop; - Console.Out.Write($"\u001b[2K\u001b[{LineToClear};0H"); - int after = Console.CursorTop; - - // Some consoles claim to be VT100 compliant, but don't respect - // all of the ANSI codes, so fallback to the System.Console impl in that case - useConsoleFallback = (before == after); - } - - if (useConsoleFallback) - { - SystemConsoleLineRewriter(); - } - } - - private void SystemConsoleLineRewriter() => Console.SetCursorPosition(0, LineToClear); - - private static bool? _isSetCursorPositionSupported; - public bool IsRewriteConsoleLineSupported - { - get - { - bool isSupported = _isSetCursorPositionSupported ?? EnsureInitialized(); - return isSupported; - - bool EnsureInitialized() - { - try - { - int left = Console.CursorLeft; - int top = Console.CursorTop; - Console.SetCursorPosition(0, LineToClear); - Console.SetCursorPosition(left, top); - _isSetCursorPositionSupported = true; - } - catch - { - _isSetCursorPositionSupported = false; - } - return (bool)_isSetCursorPositionSupported; - } - } - } - } } diff --git a/src/Tools/Common/LineRewriter.cs b/src/Tools/Common/LineRewriter.cs new file mode 100644 index 0000000000..e42e9b4a71 --- /dev/null +++ b/src/Tools/Common/LineRewriter.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.Tools.Common; + +namespace Microsoft.Internal.Common.Utils +{ + internal sealed class LineRewriter + { + public int LineToClear { get; set; } + + private IConsole Console { get; } + + public LineRewriter(IConsole console) + { + Console = console; + } + + // ANSI escape codes: + // [2K => clear current line + // [{LineToClear};0H => move cursor to column 0 of row `LineToClear` + public void RewriteConsoleLine() + { + bool useConsoleFallback = true; + if (!Console.IsInputRedirected) + { + // in case of console input redirection, the control ANSI codes would appear + + // first attempt ANSI Codes + int before = Console.CursorTop; + Console.Out.Write($"\u001b[2K\u001b[{LineToClear};0H"); + int after = Console.CursorTop; + + // Some consoles claim to be VT100 compliant, but don't respect + // all of the ANSI codes, so fallback to the System.Console impl in that case + useConsoleFallback = (before == after); + } + + if (useConsoleFallback) + { + SystemConsoleLineRewriter(); + } + } + + private void SystemConsoleLineRewriter() => Console.SetCursorPosition(0, LineToClear); + + private static bool? _isSetCursorPositionSupported; + public bool IsRewriteConsoleLineSupported + { + get + { + bool isSupported = _isSetCursorPositionSupported ?? EnsureInitialized(); + return isSupported; + + bool EnsureInitialized() + { + try + { + int left = Console.CursorLeft; + int top = Console.CursorTop; + Console.SetCursorPosition(0, LineToClear); + Console.SetCursorPosition(left, top); + _isSetCursorPositionSupported = true; + } + catch + { + _isSetCursorPositionSupported = false; + } + return (bool)_isSetCursorPositionSupported; + } + } + } + } +} diff --git a/src/Tools/dotnet-counters/dotnet-counters.csproj b/src/Tools/dotnet-counters/dotnet-counters.csproj index 727fe4183f..343eac4d61 100644 --- a/src/Tools/dotnet-counters/dotnet-counters.csproj +++ b/src/Tools/dotnet-counters/dotnet-counters.csproj @@ -22,8 +22,8 @@ - + diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj index 512a63cd8a..8276cc4d86 100644 --- a/src/Tools/dotnet-dump/dotnet-dump.csproj +++ b/src/Tools/dotnet-dump/dotnet-dump.csproj @@ -23,7 +23,6 @@ - diff --git a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj index 28181718c9..09f0fec236 100644 --- a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj +++ b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj @@ -27,7 +27,6 @@ - diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index dc21ad0d86..3bdc1fee9f 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -26,7 +26,6 @@ - From 944c6ceedd99860f23e216c85e76e3100de01ef1 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 5 Nov 2025 17:02:03 +0000 Subject: [PATCH 19/20] [CommonTools] Rename and move CommandUtils CommandUtils isn't a real Command, unlike ProcessStatus. It's more similar to other Common sources, so move and rename for consistency with class. --- src/Tools/Common/{Commands/Utils.cs => CommandUtils.cs} | 0 src/Tools/dotnet-counters/dotnet-counters.csproj | 2 +- src/Tools/dotnet-dump/dotnet-dump.csproj | 2 +- src/Tools/dotnet-gcdump/dotnet-gcdump.csproj | 2 +- src/Tools/dotnet-stack/dotnet-stack.csproj | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/Tools/Common/{Commands/Utils.cs => CommandUtils.cs} (100%) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/CommandUtils.cs similarity index 100% rename from src/Tools/Common/Commands/Utils.cs rename to src/Tools/Common/CommandUtils.cs diff --git a/src/Tools/dotnet-counters/dotnet-counters.csproj b/src/Tools/dotnet-counters/dotnet-counters.csproj index 343eac4d61..1c56c69d0b 100644 --- a/src/Tools/dotnet-counters/dotnet-counters.csproj +++ b/src/Tools/dotnet-counters/dotnet-counters.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj index 8276cc4d86..60705a32d8 100644 --- a/src/Tools/dotnet-dump/dotnet-dump.csproj +++ b/src/Tools/dotnet-dump/dotnet-dump.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj index 09f0fec236..a0b4064b9c 100644 --- a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj +++ b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj index 3bdc1fee9f..403ee760d6 100644 --- a/src/Tools/dotnet-stack/dotnet-stack.csproj +++ b/src/Tools/dotnet-stack/dotnet-stack.csproj @@ -22,7 +22,7 @@ - + From f35c402a9e1ecc5797196f92badf40d46e4051ed Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 6 Nov 2025 17:18:58 +0000 Subject: [PATCH 20/20] Cleanup remnant cast --- src/Tools/dotnet-counters/CounterMonitor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index ff694c0b08..230bea4940 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -244,7 +244,7 @@ public async Task Monitor( catch (DiagnosticToolException dte) { _console.Error.WriteLine(dte.Message); - return (ReturnCode)dte.ReturnCode; + return dte.ReturnCode; } finally { @@ -351,7 +351,7 @@ public async Task Collect( catch (DiagnosticToolException dte) { _console.Error.WriteLine(dte.Message); - return (ReturnCode)dte.ReturnCode; + return dte.ReturnCode; } finally {