From 8f85a72851b791ffe4c88d46e8def6facf2a68e7 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 31 Jul 2025 13:11:36 -0500 Subject: [PATCH 1/2] [dsrouter] improved instructions/log messages Context: https://github.com/dotnet/android/pull/10351 Context: https://github.com/dotnet/macios/pull/23429 We are adding new MSBuild properties to simplify setting `$DOTNET_DiagnosticPorts` on mobile: * `$(DiagnosticAddress)` * `$(DiagnosticPort)` * `$(DiagnosticSuspend)` * `$(DiagnosticListenMode)` * `$(DiagnosticConfiguration)` if you want to specify the full value yourself, escape `,` with `%2c`, etc. We will ship these properties in future releases of .NET 9 and 10. To improve `dsrouter` the current log message: Start an application on android device with ONE of the following environment variables set: [Default Tracing] DOTNET_DiagnosticPorts=127.0.0.1:9000,nosuspend,connect [Startup Tracing] DOTNET_DiagnosticPorts=127.0.0.1:9000,suspend,connect Will change to: Build and run an Android application such as: [Default Tracing] dotnet build -t:Run -c Release -p:DiagnosticAddress=127.0.0.1 -p:DiagnosticPort=9000 -p:DiagnosticSuspend=false -p:DiagnosticListenMode=connect [Startup Tracing] dotnet build -t:Run -c Release -p:DiagnosticAddress=127.0.0.1 -p:DiagnosticPort=9000 -p:DiagnosticSuspend=true -p:DiagnosticListenMode=connect Note that `dotnet run` *does work*, but it doesn't show good progress on the build & deploy steps as compared to `dotnet build -t:Run` and MSBuild's terminal logger. It can take several seconds to run a `Release` build. I think it's better to recommend `-t:Run` until we improve `dotnet run`. --- .../DiagnosticsServerRouterCommands.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs index 0f657df026..bc0b0a3473 100644 --- a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs +++ b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs @@ -361,7 +361,7 @@ public async Task RunIpcServerIOSSimulatorRouter(CancellationToken token, i { if (info || ParseLogLevel(verbose) <= LogLevel.Information) { - logRouterUsageInfo("ios simulator", "127.0.0.1:9000", true, parentProcess); + logRouterUsageInfo("ios simulator", "127.0.0.1", "9000", true, parentProcess); } return await RunIpcServerTcpClientRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); @@ -371,7 +371,7 @@ public async Task RunIpcServerIOSRouter(CancellationToken token, int runtim { if (info || ParseLogLevel(verbose) <= LogLevel.Information) { - logRouterUsageInfo("ios device", "127.0.0.1:9000", true, parentProcess); + logRouterUsageInfo("ios device", "127.0.0.1", "9000", true, parentProcess); } return await RunIpcServerTcpClientRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "iOS").ConfigureAwait(false); @@ -381,7 +381,7 @@ public async Task RunIpcServerAndroidEmulatorRouter(CancellationToken token { if (info || ParseLogLevel(verbose) <= LogLevel.Information) { - logRouterUsageInfo("android emulator", "10.0.2.2:9000", false, parentProcess); + logRouterUsageInfo("android emulator", "10.0.2.2", "9000", false, parentProcess); } return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); @@ -391,7 +391,7 @@ public async Task RunIpcServerAndroidRouter(CancellationToken token, int ru { if (info || ParseLogLevel(verbose) <= LogLevel.Information) { - logRouterUsageInfo("android device", "127.0.0.1:9000", false, parentProcess); + logRouterUsageInfo("android device", "127.0.0.1", "9000", false, parentProcess); } return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9001", runtimeTimeout, verbose, "Android").ConfigureAwait(false); @@ -501,7 +501,7 @@ private static LogLevel ParseLogLevel(string verbose) return logLevel; } - private static void logRouterUsageInfo(string deviceName, string deviceTcpIpAddress, bool deviceListenMode, string parentProcess) + private static void logRouterUsageInfo(string deviceName, string deviceTcpIpAddress, string deviceTcpIpPort, bool deviceListenMode, string parentProcess) { StringBuilder message = new(); @@ -509,11 +509,11 @@ private static void logRouterUsageInfo(string deviceName, string deviceTcpIpAddr int pid = Process.GetCurrentProcess().Id; message.AppendLine($"How to connect current dotnet-dsrouter pid={pid} with {deviceName} and diagnostics tooling."); - message.AppendLine($"Start an application on {deviceName} with ONE of the following environment variables set:"); + message.AppendLine($"Build and run your application on {deviceName} such as:"); message.AppendLine("[Default Tracing]"); - message.AppendLine($"DOTNET_DiagnosticPorts={deviceTcpIpAddress},nosuspend,{listenMode}"); + message.AppendLine($"dotnet build -t:Run -c Release -p:DiagnosticAddress={deviceTcpIpAddress} -p:DiagnosticPort={deviceTcpIpPort} -p:DiagnosticSuspend=false -p:DiagnosticListenMode={listenMode}"); message.AppendLine("[Startup Tracing]"); - message.AppendLine($"DOTNET_DiagnosticPorts={deviceTcpIpAddress},suspend,{listenMode}"); + message.AppendLine($"dotnet build -t:Run -c Release -p:DiagnosticAddress={deviceTcpIpAddress} -p:DiagnosticPort={deviceTcpIpPort} -p:DiagnosticSuspend=true -p:DiagnosticListenMode={listenMode}"); if (string.IsNullOrEmpty(parentProcess)) { message.AppendLine($"Run diagnotic tool connecting application on {deviceName} through dotnet-dsrouter pid={pid}:"); From 79328d24f1b1634a4b325f6ad96f57df3980da64 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 5 Aug 2025 16:20:15 -0500 Subject: [PATCH 2/2] [dotnet-trace/dsrouter] simplify mobile tracing to a one-liner! This is WIP, I have a TODO list at the bottom. This enables you to do: dotnet-trace collect --dsrouter android -- dotnet build MyApp.csproj -t:Run -c Release -p:DiagnosticSuspend=false Where `-p:DiagnosticSuspend=false` is optional. What this does: * `dotnet-trace` starts * `dotnet-trace` launches `dotnet-dsrouter` passing the `--` unmatched args along. * `dotnet-dsrouter` appends additional MSBuild properties if it detects the unmatched args are a `dotnet build` or `dotnet run` commands * `dotnet-dsrouter` launches the command for the unmatched args. ~~ TODO ~~ - [ ] Merge this one first: https://github.com/dotnet/diagnostics/pull/5535 - [ ] Figure out how we show progress. `dotnet build -t:Run` can take 30 seconds+, so it seems like we somehow need to let the terminal logger show progress? Not sure how that can work. - [ ] Switches after the `--` must be quoted in some cases: `'-t:Run'` for example. This is also a problem with plain `dotnet-trace`. This could maybe be addressed in a separate PR. --- src/Tools/Common/Commands/Utils.cs | 9 +- src/Tools/Common/DsRouterProcessLauncher.cs | 8 +- .../ReversedServerHelpers.cs | 11 +++ src/Tools/dotnet-counters/CounterMonitor.cs | 10 +- src/Tools/dotnet-counters/Program.cs | 7 +- .../DiagnosticsServerRouterCommands.cs | 97 ++++++++++++++----- src/Tools/dotnet-dsrouter/Program.cs | 36 +++++-- src/Tools/dotnet-dump/Dumper.cs | 2 +- .../CommandLine/CollectCommandHandler.cs | 10 +- .../CommandLine/ReportCommandHandler.cs | 3 +- .../CommandLine/Commands/CollectCommand.cs | 12 ++- src/Tools/dotnet-trace/Program.cs | 3 +- .../CounterMonitorPayloadTests.cs | 4 +- 13 files changed, 160 insertions(+), 52 deletions(-) diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index 0b28a7ff2e..6910899167 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -44,11 +44,11 @@ public static int FindProcessIdWithName(string name) // // dsrouterCommand // processId - public static int LaunchDSRouterProcess(string dsrouterCommand) + public static int LaunchDSRouterProcess(string dsrouterCommand, IReadOnlyList unmatchedTokens) { Console.WriteLine("For finer control over the dotnet-dsrouter options, run it separately and connect to it using -p" + Environment.NewLine); - return DsRouterProcessLauncher.Launcher.Start(dsrouterCommand, default); + return DsRouterProcessLauncher.Launcher.Start(dsrouterCommand, unmatchedTokens, default); } @@ -80,9 +80,10 @@ public static bool ValidateArgumentsForChildProcess(int processId, string name, /// name /// port /// dsrouter + /// unmatchedTokens /// resolvedProcessId /// - public static 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, IReadOnlyList unmatchedTokens, out int resolvedProcessId) { resolvedProcessId = -1; if (processId == 0 && string.IsNullOrEmpty(name) && string.IsNullOrEmpty(port) && string.IsNullOrEmpty(dsrouter)) @@ -124,7 +125,7 @@ public static bool ResolveProcessForAttach(int processId, string name, string po Console.WriteLine("Invalid value for --dsrouter. Valid values are 'ios', 'ios-sim', 'android' and 'android-emu'."); return false; } - if ((processId = LaunchDSRouterProcess(dsrouter)) < 0) + if ((processId = LaunchDSRouterProcess(dsrouter, unmatchedTokens)) < 0) { if (processId == -2) { diff --git a/src/Tools/Common/DsRouterProcessLauncher.cs b/src/Tools/Common/DsRouterProcessLauncher.cs index e4c93efa30..4ef738d2d5 100644 --- a/src/Tools/Common/DsRouterProcessLauncher.cs +++ b/src/Tools/Common/DsRouterProcessLauncher.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Binding; using System.Diagnostics; @@ -117,7 +118,7 @@ private static async Task ReadAndLogAllLinesAsync(StreamReader streamToRead, Tex private Process ChildProc => _childProc; - public int Start(string dsrouterCommand, CancellationToken ct) + public int Start(string dsrouterCommand, IReadOnlyList unmatchedTokens, CancellationToken ct) { string toolsRoot = System.IO.Path.GetDirectoryName(System.Environment.ProcessPath); string dotnetDsrouterTool = "dotnet-dsrouter"; @@ -132,6 +133,11 @@ public int Start(string dsrouterCommand, CancellationToken ct) // Block SIGINT and SIGQUIT in child process. dsrouterCommand += $" --block-signals SIGINT;SIGQUIT --parentprocess \"{currentProcess.Id}:{currentProcess.ProcessName}\""; + if (unmatchedTokens.Count > 0) + { + dsrouterCommand += $" -- {string.Join(" ", unmatchedTokens)}"; + } + _childProc = new Process(); _childProc.StartInfo.FileName = dotnetDsrouterTool; diff --git a/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs b/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs index b51f0cb130..5b7580d214 100644 --- a/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs +++ b/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -32,6 +33,16 @@ public void PrepareChildProcess(string[] args) return; } + PrepareChildProcess(args, unparsedTokenIdx); + } + + public void PrepareChildProcess(IEnumerable unmatchedTokens) + { + PrepareChildProcess([.. unmatchedTokens], 0); + } + + private void PrepareChildProcess(string[] args, int unparsedTokenIdx) + { _childProc = new Process(); _childProc.StartInfo.FileName = args[unparsedTokenIdx]; string arguments = ""; diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index 9a6e476aae..bdb0c13431 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -176,7 +176,8 @@ public async Task Monitor( int maxTimeSeries, TimeSpan duration, bool showDeltas, - string dsrouter) + string dsrouter, + IReadOnlyList unmatchedTokens) { try { @@ -186,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, unmatchedTokens, out _processId)) { return ReturnCode.ArgumentError; } @@ -261,7 +262,8 @@ public async Task Collect( int maxHistograms, int maxTimeSeries, TimeSpan duration, - string dsrouter) + string dsrouter, + IReadOnlyList unmatchedTokens) { try { @@ -271,7 +273,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, unmatchedTokens, out _processId)) { return ReturnCode.ArgumentError; } diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs index b15dca5412..e1471297c2 100644 --- a/src/Tools/dotnet-counters/Program.cs +++ b/src/Tools/dotnet-counters/Program.cs @@ -50,7 +50,8 @@ private static Command MonitorCommand() maxTimeSeries: parseResult.GetValue(MaxTimeSeriesOption), duration: parseResult.GetValue(DurationOption), showDeltas: parseResult.GetValue(ShowDeltasOption), - dsrouter: string.Empty + dsrouter: string.Empty, + unmatchedTokens: parseResult.UnmatchedTokens )); return monitorCommand; @@ -90,7 +91,9 @@ private static Command CollectCommand() maxHistograms: parseResult.GetValue(MaxHistogramOption), maxTimeSeries: parseResult.GetValue(MaxTimeSeriesOption), duration: parseResult.GetValue(DurationOption), - dsrouter: string.Empty)); + dsrouter: string.Empty, + unmatchedTokens: parseResult.UnmatchedTokens + )); return collectCommand; } diff --git a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs index bc0b0a3473..97b55f9f1a 100644 --- a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs +++ b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -357,44 +358,67 @@ public async Task RunIpcClientWebSocketServerRouter(CancellationToken token } } - public async Task RunIpcServerIOSSimulatorRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info, string parentProcess) + public async Task RunIpcServerIOSSimulatorRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info, string parentProcess, IReadOnlyList unmatchedTokens) { + string deviceTcpIpAddress = "127.0.0.1"; + string deviceTcpIpPort = "9000"; + string listenMode = "listen"; + if (info || ParseLogLevel(verbose) <= LogLevel.Information) { - logRouterUsageInfo("ios simulator", "127.0.0.1", "9000", true, parentProcess); + logRouterUsageInfo("ios simulator", deviceTcpIpAddress, deviceTcpIpPort, listenMode, parentProcess); } + prepareChildProcess(unmatchedTokens, deviceTcpIpAddress, deviceTcpIpPort, listenMode); - return await RunIpcServerTcpClientRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); + return await RunIpcServerTcpClientRouter(token, "", $"{deviceTcpIpAddress}:{deviceTcpIpPort}", runtimeTimeout, verbose, "").ConfigureAwait(false); } - public async Task RunIpcServerIOSRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info, string parentProcess) + public async Task RunIpcServerIOSRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info, string parentProcess, IReadOnlyList unmatchedTokens) { + string deviceTcpIpAddress = "127.0.0.1"; + string deviceTcpIpPort = "9000"; + string listenMode = "listen"; + if (info || ParseLogLevel(verbose) <= LogLevel.Information) { - logRouterUsageInfo("ios device", "127.0.0.1", "9000", true, parentProcess); + logRouterUsageInfo("ios device", deviceTcpIpAddress, deviceTcpIpPort, listenMode, parentProcess); } - return await RunIpcServerTcpClientRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "iOS").ConfigureAwait(false); + prepareChildProcess(unmatchedTokens, deviceTcpIpAddress, deviceTcpIpPort, listenMode); + + return await RunIpcServerTcpClientRouter(token, "", $"{deviceTcpIpAddress}:{deviceTcpIpPort}", runtimeTimeout, verbose, "iOS").ConfigureAwait(false); } - public async Task RunIpcServerAndroidEmulatorRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info, string parentProcess) + public async Task RunIpcServerAndroidEmulatorRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info, string parentProcess, IReadOnlyList unmatchedTokens) { + string deviceTcpIpAddress = "10.0.2.2"; + string deviceTcpIpPort = "9000"; + string listenMode = "connect"; + if (info || ParseLogLevel(verbose) <= LogLevel.Information) { - logRouterUsageInfo("android emulator", "10.0.2.2", "9000", false, parentProcess); + logRouterUsageInfo("android emulator", deviceTcpIpAddress, deviceTcpIpPort, listenMode, parentProcess); } - return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); + prepareChildProcess(unmatchedTokens, deviceTcpIpAddress, deviceTcpIpPort, listenMode); + + return await RunIpcServerTcpServerRouter(token, "", $"127.0.0.1:{deviceTcpIpPort}", runtimeTimeout, verbose, "").ConfigureAwait(false); } - public async Task RunIpcServerAndroidRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info, string parentProcess) + public async Task RunIpcServerAndroidRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info, string parentProcess, IReadOnlyList unmatchedTokens) { + string deviceTcpIpAddress = "127.0.0.1"; + string deviceTcpIpPort = "9000"; + string listenMode = "connect"; + if (info || ParseLogLevel(verbose) <= LogLevel.Information) { - logRouterUsageInfo("android device", "127.0.0.1", "9000", false, parentProcess); + logRouterUsageInfo("android device", deviceTcpIpAddress, deviceTcpIpPort, listenMode, parentProcess); } - return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9001", runtimeTimeout, verbose, "Android").ConfigureAwait(false); + prepareChildProcess(unmatchedTokens, deviceTcpIpAddress, deviceTcpIpPort, listenMode); + + return await RunIpcServerTcpServerRouter(token, "", $"{deviceTcpIpAddress}:{deviceTcpIpPort}", runtimeTimeout, verbose, "Android").ConfigureAwait(false); } private static string GetDefaultIpcServerPath(ILogger logger) @@ -501,11 +525,9 @@ private static LogLevel ParseLogLevel(string verbose) return logLevel; } - private static void logRouterUsageInfo(string deviceName, string deviceTcpIpAddress, string deviceTcpIpPort, bool deviceListenMode, string parentProcess) + private static void logRouterUsageInfo(string deviceName, string deviceTcpIpAddress, string deviceTcpIpPort, string listenMode, string parentProcess) { StringBuilder message = new(); - - string listenMode = deviceListenMode ? "listen" : "connect"; int pid = Process.GetCurrentProcess().Id; message.AppendLine($"How to connect current dotnet-dsrouter pid={pid} with {deviceName} and diagnostics tooling."); @@ -521,10 +543,44 @@ private static void logRouterUsageInfo(string deviceName, string deviceTcpIpAddr } message.AppendLine($"See https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter for additional details and examples."); + logMessageWithColor(message.ToString(), ConsoleColor.Green); + } + + private static void logMessageWithColor(string message, ConsoleColor color) + { ConsoleColor currentColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(message.ToString()); - Console.ForegroundColor = currentColor; + try + { + Console.ForegroundColor = color; + Console.WriteLine(message); + } + finally + { + Console.ForegroundColor = currentColor; + } + } + + private static void prepareChildProcess(IReadOnlyList unmatchedTokens, string deviceTcpIpAddress, string deviceTcpIpPort, string listenMode) + { + if (unmatchedTokens.Count == 0) + { + return; + } + List args = [.. unmatchedTokens]; + if (unmatchedTokens.Count > 1) + { + // Automatically add MSBuild properties for 'dotnet run' or 'dotnet build' commands + if (args[1] == "run" || args[1] == "build") + { + args.Add($"-p:DiagnosticAddress={deviceTcpIpAddress}"); + args.Add($"-p:DiagnosticPort={deviceTcpIpPort}"); + args.Add($"-p:DiagnosticListenMode={listenMode}"); + // NOTE: -p:DiagnosticSuspend has to be provided by the user + } + } + + logMessageWithColor($"Running: {string.Join(' ', args)}", ConsoleColor.Green); + ProcessLauncher.Launcher.PrepareChildProcess(args); } private static void checkLoopbackOnly(string tcpServer, LogLevel logLevel) @@ -540,10 +596,7 @@ private static void checkLoopbackOnly(string tcpServer, LogLevel logLevel) message.Append("testing environments."); message.AppendLine(); - ConsoleColor currentColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(message.ToString()); - Console.ForegroundColor = currentColor; + logMessageWithColor(message.ToString(), ConsoleColor.Yellow); } } } diff --git a/src/Tools/dotnet-dsrouter/Program.cs b/src/Tools/dotnet-dsrouter/Program.cs index bfd3deecfd..d0f0edb3c1 100644 --- a/src/Tools/dotnet-dsrouter/Program.cs +++ b/src/Tools/dotnet-dsrouter/Program.cs @@ -158,13 +158,15 @@ private static Command IOSSimulatorRouterCommand() { RuntimeTimeoutOption, VerboseOption, InfoOption, BlockedSignalsOption, ParentProcessOption }; + command.TreatUnmatchedTokensAsErrors = false; command.SetAction((parseResult, ct) => new DiagnosticsServerRouterCommands().RunIpcServerIOSSimulatorRouter( ct, runtimeTimeout: parseResult.GetValue(RuntimeTimeoutOption), verbose: parseResult.GetValue(VerboseOption), info: parseResult.GetValue(InfoOption), - parentProcess: parseResult.GetValue(ParentProcessOption) + parentProcess: parseResult.GetValue(ParentProcessOption), + unmatchedTokens: parseResult.UnmatchedTokens )); return command; @@ -180,13 +182,15 @@ private static Command IOSRouterCommand() { RuntimeTimeoutOption, VerboseOption, InfoOption, BlockedSignalsOption, ParentProcessOption }; + command.TreatUnmatchedTokensAsErrors = false; command.SetAction((parseResult, ct) => new DiagnosticsServerRouterCommands().RunIpcServerIOSRouter( ct, runtimeTimeout: parseResult.GetValue(RuntimeTimeoutOption), verbose: parseResult.GetValue(VerboseOption), info: parseResult.GetValue(InfoOption), - parentProcess: parseResult.GetValue(ParentProcessOption) + parentProcess: parseResult.GetValue(ParentProcessOption), + unmatchedTokens: parseResult.UnmatchedTokens )); return command; @@ -202,13 +206,15 @@ private static Command AndroidEmulatorRouterCommand() { RuntimeTimeoutOption, VerboseOption, InfoOption, BlockedSignalsOption, ParentProcessOption }; + command.TreatUnmatchedTokensAsErrors = false; command.SetAction((parseResult, ct) => new DiagnosticsServerRouterCommands().RunIpcServerAndroidEmulatorRouter( ct, runtimeTimeout: parseResult.GetValue(RuntimeTimeoutOption), verbose: parseResult.GetValue(VerboseOption), info: parseResult.GetValue(InfoOption), - parentProcess: parseResult.GetValue(ParentProcessOption) + parentProcess: parseResult.GetValue(ParentProcessOption), + unmatchedTokens: parseResult.UnmatchedTokens )); return command; @@ -224,13 +230,15 @@ private static Command AndroidRouterCommand() { RuntimeTimeoutOption, VerboseOption, InfoOption, BlockedSignalsOption, ParentProcessOption }; + command.TreatUnmatchedTokensAsErrors = false; command.SetAction((parseResult, ct) => new DiagnosticsServerRouterCommands().RunIpcServerAndroidRouter( ct, runtimeTimeout: parseResult.GetValue(RuntimeTimeoutOption), verbose: parseResult.GetValue(VerboseOption), info: parseResult.GetValue(InfoOption), - parentProcess: parseResult.GetValue(ParentProcessOption) + parentProcess: parseResult.GetValue(ParentProcessOption), + unmatchedTokens: parseResult.UnmatchedTokens )); return command; @@ -320,6 +328,11 @@ private static Command AndroidRouterCommand() private static Task Main(string[] args) { + Command iosCommand = IOSRouterCommand(); + Command iosSimulatorCommand = IOSSimulatorRouterCommand(); + Command androidCommand = AndroidRouterCommand(); + Command androidEmulatorCommand = AndroidEmulatorRouterCommand(); + RootCommand rootCommand = new() { IpcClientTcpServerRouterCommand(), @@ -328,15 +341,20 @@ private static Task Main(string[] args) IpcClientTcpClientRouterCommand(), IpcServerWebSocketServerRouterCommand(), IpcClientWebSocketServerRouterCommand(), - IOSSimulatorRouterCommand(), - IOSRouterCommand(), - AndroidEmulatorRouterCommand(), - AndroidRouterCommand() + iosSimulatorCommand, + iosCommand, + androidEmulatorCommand, + androidCommand }; ParseResult parseResult = rootCommand.Parse(args); - if (parseResult.UnmatchedTokens.Count > 0) + string parsedCommandName = parseResult.CommandResult.Command.Name; + if (parseResult.UnmatchedTokens.Count > 0 && + parsedCommandName != iosCommand.Name && + parsedCommandName != iosSimulatorCommand.Name && + parsedCommandName != androidCommand.Name && + parsedCommandName != androidEmulatorCommand.Name) { ProcessLauncher.Launcher.PrepareChildProcess(args); } diff --git a/src/Tools/dotnet-dump/Dumper.cs b/src/Tools/dotnet-dump/Dumper.cs index 65c30d32f3..7ed658d958 100644 --- a/src/Tools/dotnet-dump/Dumper.cs +++ b/src/Tools/dotnet-dump/Dumper.cs @@ -35,7 +35,7 @@ public int Collect(TextWriter stdOutput, TextWriter stdError, int processId, str { try { - 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-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index 26af23d866..865ddbce61 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.CommandLine; using System.IO; using System.Threading; @@ -27,10 +28,11 @@ internal static class CollectCommandHandler /// The process name to collect the gcdump from. /// The diagnostic IPC channel to collect the gcdump from. /// The dsrouter command to use for collecting the gcdump. + /// Unmatched tokens from the command line. /// - private static async Task Collect(CancellationToken ct, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, string dsrouter) + private static async Task Collect(CancellationToken ct, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, string dsrouter, IReadOnlyList unmatchedTokens) { - if (!CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId)) + if (!CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, unmatchedTokens, out int resolvedProcessId)) { return -1; } @@ -155,7 +157,9 @@ public static Command CollectCommand() verbose: parseResult.GetValue(VerboseOption), name: parseResult.GetValue(NameOption), diagnosticPort: parseResult.GetValue(DiagnosticPortOption) ?? string.Empty, - dsrouter: parseResult.GetValue(DsRouterOption) ?? string.Empty)); + dsrouter: parseResult.GetValue(DsRouterOption) ?? string.Empty, + unmatchedTokens: parseResult.UnmatchedTokens + )); return collectCommand; } diff --git a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs index c0bab3af39..3936bc050d 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Binding; using System.IO; @@ -91,7 +92,7 @@ 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)) + if (!CommandUtils.ResolveProcessForAttach(processId, string.Empty, diagnosticPort, dsrouter, [], out int resolvedProcessId)) { return Task.FromResult(-1); } diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index 054a6e83a7..1c60ab0379 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -53,8 +53,10 @@ private static void ConsoleWriteLine(string str) /// A string, parsed as-is, that will stop the trace upon hitting an event with the matching event name. Requires `--stopping-event-provider-name` to be set. For a more specific stopping event, additionally provide `--stopping-event-payload-filter`. /// A string, parsed as [payload_field_name]:[payload_field_value] pairs separated by commas, that will stop the trace upon hitting an event with a matching payload. Requires `--stopping-event-provider-name` and `--stopping-event-event-name` to be set. /// Collect rundown events. + /// Path to the data collector router to be used. + /// Any unmatched tokens from the command line. /// - private static async Task Collect(CancellationToken ct, CommandLineConfiguration cliConfig, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio, bool resumeRuntime, string stoppingEventProviderName, string stoppingEventEventName, string stoppingEventPayloadFilter, bool? rundown, string dsrouter) + private static async Task Collect(CancellationToken ct, CommandLineConfiguration cliConfig, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio, bool resumeRuntime, string stoppingEventProviderName, string stoppingEventEventName, string stoppingEventPayloadFilter, bool? rundown, string dsrouter, IReadOnlyList unmatchedTokens) { bool collectionStopped = false; bool cancelOnEnter = true; @@ -94,7 +96,7 @@ private static async Task Collect(CancellationToken ct, CommandLineConfigur 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, unmatchedTokens, out int resolvedProcessId)) { processId = resolvedProcessId; } @@ -605,11 +607,15 @@ public static Command CollectCommand() stoppingEventEventName: parseResult.GetValue(StoppingEventEventNameOption), stoppingEventPayloadFilter: parseResult.GetValue(StoppingEventPayloadFilterOption), rundown: parseResult.GetValue(RundownOption), - dsrouter: parseResult.GetValue(DSRouterOption))); + dsrouter: GetDSRouterOption(parseResult), + unmatchedTokens: parseResult.UnmatchedTokens)); return collectCommand; } + public static string GetDSRouterOption(ParseResult parseResult) => + parseResult.GetValue(DSRouterOption) ?? string.Empty; + private const uint DefaultCircularBufferSizeInMB = 256; private static readonly Option CircularBufferOption = diff --git a/src/Tools/dotnet-trace/Program.cs b/src/Tools/dotnet-trace/Program.cs index 69c3cff0be..4e4d0eaa26 100644 --- a/src/Tools/dotnet-trace/Program.cs +++ b/src/Tools/dotnet-trace/Program.cs @@ -29,8 +29,9 @@ public static Task Main(string[] args) if (parsedCommandName == "collect") { IReadOnlyCollection unparsedTokens = parseResult.UnmatchedTokens; + string dsrouter = CollectCommandHandler.GetDSRouterOption(parseResult); // If we notice there are unparsed tokens, user might want to attach on startup. - if (unparsedTokens.Count > 0) + if (string.IsNullOrEmpty(dsrouter) && unparsedTokens.Count > 0) { ProcessLauncher.Launcher.PrepareChildProcess(args); } diff --git a/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs b/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs index f1f6b554c0..efb1e51d74 100644 --- a/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs +++ b/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs @@ -217,7 +217,9 @@ await monitor.Collect( maxHistograms: 10, maxTimeSeries: 1000, duration: TimeSpan.FromSeconds(10), - dsrouter: null)); + dsrouter: null, + unmatchedTokens: [] + )); }, testRunner, source.Token); return CreateMetricComponents();