From: Johan Lorensson Date: Tue, 29 Aug 2023 18:18:47 +0000 (+0200) Subject: Add default iOS/Android commands to dotnet-dsrouter. (#4090) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055542~37^2^2~60 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=580381de948288fb81a6c2680aa760fc542faa3a;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add default iOS/Android commands to dotnet-dsrouter. (#4090) dotnet-dsrouter can be a little hard to configure for mobile use cases since it needs a number of arguments, both to setup its local IPC client|server and the corresponding TCP client|server and what arguments to use depends on what mobile platform and scenario the user runs. There are currently a number of different scenarios described in different sources of documentation: Runtime docs: https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md iOS SDK docs: https://github.com/xamarin/xamarin-macios/wiki/Profiling Android SDK docs: https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/tracing.md They all fall into a number of predefined scenarios and a number of "default" parameters that should be used. This PR creates 4 new commands in dotnet-dsrouter to explicitly use the defaults described in the documentation, ios-sim, ios, android-emu, android. They all fallback to default IPC server listener for dsrouter and in order to make that simpler in use with diagnostic tooling, changes done in https://github.com/dotnet/diagnostics/pull/4081 is also needed to simplify the process. So lets take an example form the docs, running an app on iOS simulator. Before this PR the following dsrouter command needs to be run: ``` dotnet-dsrouter client-server -ipcc ~/my-sim-port -tcps 127.0.0.1:9000 launch app with DOTNET_DiagnosticPorts=127.0.0.1:9000 dotnet-trace collect --diagnostic-port ~/my-sim-port --format speedscope ``` With this PR (and https://github.com/dotnet/diagnostics/pull/4081) the above will look like: ``` dotnet-dsrouter ios-sim launch app with DOTNET_DiagnosticPorts=127.0.0.1:9000 dotnet-trace collect -p: --format speedscope ``` dontet-dsrouter will output both its pid as well as what DOTNET_DiagnosticPorts that can be used to connect to it on startup. Using a different mobile platform/scenario, like android emulator is pretty much identical, just switch ios-sim to android-emu. dotnet-dsrouter will output the exact DOTNET_DiagnosticPorts that should be used with any of the configured scenarios. --- diff --git a/src/Tools/dotnet-dsrouter/ADBTcpRouterFactory.cs b/src/Tools/dotnet-dsrouter/ADBTcpRouterFactory.cs index e910be887..5177e6b30 100644 --- a/src/Tools/dotnet-dsrouter/ADBTcpRouterFactory.cs +++ b/src/Tools/dotnet-dsrouter/ADBTcpRouterFactory.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Extensions.Logging; @@ -91,8 +92,9 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter { processStartedResult = process.Start(); } - catch (Exception) + catch (Exception ex) { + logger.LogError($"Failed executing {adbTool} {command}. Error: {ex.Message}."); } if (processStartedResult) @@ -107,12 +109,12 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter if (!string.IsNullOrEmpty(stdout)) { - logger.LogTrace($"stdout: {stdout}"); + logger.LogTrace($"stdout: {stdout.TrimEnd()}"); } if (!string.IsNullOrEmpty(stderr)) { - logger.LogError($"stderr: {stderr}"); + logger.LogError($"stderr: {stderr.TrimEnd()}"); } } @@ -130,6 +132,8 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter { private readonly int _port; private bool _ownsPortReverse; + private Task _portReverseTask; + private CancellationTokenSource _portReverseTaskCancelToken; public static TcpServerRouterFactory CreateADBInstance(string tcpServer, int runtimeTimeoutMs, ILogger logger) { @@ -147,6 +151,19 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter // Enable port reverse. _ownsPortReverse = ADBCommandExec.AdbAddPortReverse(_port, Logger); + _portReverseTaskCancelToken = new CancellationTokenSource(); + _portReverseTask = Task.Run(async () => { + using PeriodicTimer timer = new(TimeSpan.FromSeconds(5)); + while (await timer.WaitForNextTickAsync(_portReverseTaskCancelToken.Token).ConfigureAwait(false) && !_portReverseTaskCancelToken.Token.IsCancellationRequested) + { + // Make sure reverse port configuration is still active. + if (ADBCommandExec.AdbAddPortReverse(_port, Logger) && !_ownsPortReverse) + { + _ownsPortReverse = true; + } + } + }, _portReverseTaskCancelToken.Token); + base.Start(); } @@ -154,6 +171,13 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter { await base.Stop().ConfigureAwait(false); + try + { + _portReverseTaskCancelToken.Cancel(); + await _portReverseTask.ConfigureAwait(false); + } + catch { } + // Disable port reverse. ADBCommandExec.AdbRemovePortReverse(_port, _ownsPortReverse, Logger); _ownsPortReverse = false; @@ -164,6 +188,8 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter { private readonly int _port; private bool _ownsPortForward; + private Task _portForwardTask; + private CancellationTokenSource _portForwardTaskCancelToken; public static TcpClientRouterFactory CreateADBInstance(string tcpClient, int runtimeTimeoutMs, ILogger logger) { @@ -180,10 +206,30 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter { // Enable port forwarding. _ownsPortForward = ADBCommandExec.AdbAddPortForward(_port, _logger); + + _portForwardTaskCancelToken = new CancellationTokenSource(); + _portForwardTask = Task.Run(async () => { + using PeriodicTimer timer = new(TimeSpan.FromSeconds(5)); + while (await timer.WaitForNextTickAsync(_portForwardTaskCancelToken.Token).ConfigureAwait(false) && !_portForwardTaskCancelToken.Token.IsCancellationRequested) + { + // Make sure forward port configuration is still active. + if (ADBCommandExec.AdbAddPortForward(_port, _logger) && !_ownsPortForward) + { + _ownsPortForward = true; + } + } + }, _portForwardTaskCancelToken.Token); } public override void Stop() { + try + { + _portForwardTaskCancelToken.Cancel(); + _portForwardTask.Wait(); + } + catch { } + // Disable port forwarding. ADBCommandExec.AdbRemovePortForward(_port, _ownsPortForward, _logger); _ownsPortForward = false; diff --git a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs index 59692a9e1..5b83ceda8 100644 --- a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs +++ b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs @@ -347,6 +347,30 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter } } + public async Task RunIpcServerIOSSimulatorRouter(CancellationToken token, int runtimeTimeout, string verbose) + { + logDiagnosticPortsConfiguration("ios simulator", "127.0.0.1:9000", false, verbose); + return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); + } + + public async Task RunIpcServerIOSRouter(CancellationToken token, int runtimeTimeout, string verbose) + { + logDiagnosticPortsConfiguration("ios device", "127.0.0.1:9000", true, verbose); + return await RunIpcServerTcpClientRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "iOS").ConfigureAwait(false); + } + + public async Task RunIpcServerAndroidEmulatorRouter(CancellationToken token, int runtimeTimeout, string verbose) + { + logDiagnosticPortsConfiguration("android emulator", "10.0.2.2:9000", false, verbose); + return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); + } + + public async Task RunIpcServerAndroidRouter(CancellationToken token, int runtimeTimeout, string verbose) + { + logDiagnosticPortsConfiguration("android emulator", "127.0.0.1:9000", false, verbose); + return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "Android").ConfigureAwait(false); + } + private static string GetDefaultIpcServerPath(ILogger logger) { string path = string.Empty; @@ -412,6 +436,23 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter return tcpServerRouterFactory; } + private static void logDiagnosticPortsConfiguration(string deviceName, string deviceTcpIpAddress, bool deviceListenMode, string verbose) + { + StringBuilder message = new(); + + if (!string.IsNullOrEmpty(verbose)) + { + deviceName = !string.IsNullOrEmpty(deviceName) ? $" on {deviceName} " : " "; + message.AppendLine($"Start an application{deviceName}with one of the following environment variables set:"); + } + + string listenMode = deviceListenMode ? ",listen" : ",connect"; + message.AppendLine($"DOTNET_DiagnosticPorts={deviceTcpIpAddress},nosuspend{listenMode}"); + message.AppendLine($"DOTNET_DiagnosticPorts={deviceTcpIpAddress},suspend{listenMode}"); + + Console.WriteLine(message.ToString()); + } + private static void checkLoopbackOnly(string tcpServer) { if (!string.IsNullOrEmpty(tcpServer) && !DiagnosticsServerRouterRunner.isLoopbackOnly(tcpServer)) diff --git a/src/Tools/dotnet-dsrouter/Program.cs b/src/Tools/dotnet-dsrouter/Program.cs index c5fe75b1f..4ed1a3ee9 100644 --- a/src/Tools/dotnet-dsrouter/Program.cs +++ b/src/Tools/dotnet-dsrouter/Program.cs @@ -27,6 +27,14 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter private delegate Task DiagnosticsServerIpcClientWebSocketServerRouterDelegate(CancellationToken ct, string ipcClient, string webSocket, int runtimeTimeoutS, string verbose); + private delegate Task DiagnosticsServerIpcServerIOSSimulatorRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose); + + private delegate Task DiagnosticsServerIpcServerIOSRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose); + + private delegate Task DiagnosticsServerIpcServerAndroidEmulatorRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose); + + private delegate Task DiagnosticsServerIpcServerAndroidRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose); + private static Command IpcClientTcpServerRouterCommand() => new( name: "client-server", @@ -104,6 +112,58 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter IpcClientAddressOption(), TcpClientAddressOption(), RuntimeTimeoutOption(), VerboseOption(), ForwardPortOption() }; + private static Command IOSSimulatorRouterCommand() => + new( + name: "ios-sim", + description: "Start a .NET application Diagnostics Server routing local IPC server <--> iOS Simulator. " + + "Router is configured using an IPC server (connecting to by diagnostic tools) " + + "and a TCP/IP server (accepting runtime TCP client).") + { + // Handler + HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerIOSSimulatorRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcServerIOSSimulatorRouter).GetCommandHandler(), + // Options + RuntimeTimeoutOption(), VerboseOption() + }; + + private static Command IOSRouterCommand() => + new( + name: "ios", + description: "Start a .NET application Diagnostics Server routing local IPC server <--> iOS Device over usbmux. " + + "Router is configured using an IPC server (connecting to by diagnostic tools) " + + "and a TCP/IP client (connecting runtime TCP server over usbmux).") + { + // Handler + HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerIOSRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcServerIOSRouter).GetCommandHandler(), + // Options + RuntimeTimeoutOption(), VerboseOption() + }; + + private static Command AndroidEmulatorRouterCommand() => + new( + name: "android-emu", + description: "Start a .NET application Diagnostics Server routing local IPC server <--> Android Emulator. " + + "Router is configured using an IPC server (connecting to by diagnostic tools) " + + "and a TCP/IP server (accepting runtime TCP client).") + { + // Handler + HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerAndroidEmulatorRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcServerAndroidEmulatorRouter).GetCommandHandler(), + // Options + RuntimeTimeoutOption(), VerboseOption() + }; + + private static Command AndroidRouterCommand() => + new( + name: "android", + description: "Start a .NET application Diagnostics Server routing local IPC server <--> Android Device. " + + "Router is configured using an IPC server (connecting to by diagnostic tools) " + + "and a TCP/IP server (accepting runtime TCP client).") + { + // Handler + HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerAndroidRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcServerAndroidRouter).GetCommandHandler(), + // Options + RuntimeTimeoutOption(), VerboseOption() + }; + private static Option IpcClientAddressOption() => new( aliases: new[] { "--ipc-client", "-ipcc" }, @@ -193,6 +253,10 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter .AddCommand(IpcClientTcpClientRouterCommand()) .AddCommand(IpcServerWebSocketServerRouterCommand()) .AddCommand(IpcClientWebSocketServerRouterCommand()) + .AddCommand(IOSSimulatorRouterCommand()) + .AddCommand(IOSRouterCommand()) + .AddCommand(AndroidEmulatorRouterCommand()) + .AddCommand(AndroidRouterCommand()) .UseDefaults() .Build();