Skip to content

Commit a29f26d

Browse files
Launch dsrouter from dotnet-trace (#5241)
This PR aim to simplify the process of collecting traces for maui mobile scenarios and reduce the number of steps for simple cases (when a dsrouter isn't already running). It adds a --dsrouter option to collect that takes one of the following values: android, android-emu, ios, ios-sim This doesn't make dotnet-trace depends on dotnet-dsrouter, the option checks for the existence of dsrotuer in the same path as trace, and invokes it.
1 parent 0be9305 commit a29f26d

File tree

13 files changed

+173
-44
lines changed

13 files changed

+173
-44
lines changed

src/Tools/Common/Commands/Utils.cs

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,23 @@ public static int FindProcessIdWithName(string name)
3939
return commonId;
4040
}
4141

42+
// <summary>
43+
// Returns processId that matches the given dsrouter.
44+
// </summary>
45+
// <param name="dsrouter">dsroutercommand</param>
46+
// <returns>processId</returns>
47+
public static int LaunchDSRouterProcess(string dsroutercommand)
48+
{
49+
ConsoleColor currentColor = Console.ForegroundColor;
50+
Console.ForegroundColor = ConsoleColor.Yellow;
51+
Console.WriteLine("WARNING: dotnet-dsrouter is a development tool not intended for production environments.");
52+
Console.ForegroundColor = currentColor;
53+
Console.WriteLine("For finer control over the dotnet-dsrouter options, run it separately and connect to it using -p" + Environment.NewLine);
54+
55+
return DsRouterProcessLauncher.Launcher.Start(dsroutercommand, default);
56+
}
57+
58+
4259
/// <summary>
4360
/// A helper method for validating --process-id, --name, --diagnostic-port options for collect with child process commands.
4461
/// None of these options can be specified, so it checks for them and prints the appropriate error message.
@@ -59,66 +76,69 @@ public static bool ValidateArgumentsForChildProcess(int processId, string name,
5976
}
6077

6178
/// <summary>
62-
/// A helper method for validating --process-id, --name, --diagnostic-port options for collect commands.
79+
/// A helper method for validating --process-id, --name, --diagnostic-port, --dsrouter options for collect commands and resolving the process ID.
6380
/// Only one of these options can be specified, so it checks for duplicate options specified and if there is
6481
/// such duplication, it prints the appropriate error message.
6582
/// </summary>
6683
/// <param name="processId">process ID</param>
6784
/// <param name="name">name</param>
6885
/// <param name="port">port</param>
86+
/// <param name="dsrouter">dsrouter</param>
6987
/// <param name="resolvedProcessId">resolvedProcessId</param>
7088
/// <returns></returns>
71-
public static bool ValidateArgumentsForAttach(int processId, string name, string port, out int resolvedProcessId)
89+
public static bool ResolveProcessForAttach(int processId, string name, string port, string dsrouter, out int resolvedProcessId)
7290
{
7391
resolvedProcessId = -1;
74-
if (processId == 0 && string.IsNullOrEmpty(name) && string.IsNullOrEmpty(port))
92+
if (processId == 0 && string.IsNullOrEmpty(name) && string.IsNullOrEmpty(port) && string.IsNullOrEmpty(dsrouter))
7593
{
76-
Console.WriteLine("Must specify either --process-id, --name, or --diagnostic-port.");
94+
Console.WriteLine("Must specify either --process-id, --name, --diagnostic-port, or --dsrouter.");
7795
return false;
7896
}
7997
else if (processId < 0)
8098
{
8199
Console.WriteLine($"{processId} is not a valid process ID");
82100
return false;
83101
}
84-
else if (processId != 0 && !string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(port))
102+
else if ( processId != 0 && (!string.IsNullOrEmpty(name) || !string.IsNullOrEmpty(port) || !string.IsNullOrEmpty(dsrouter))
103+
|| !string.IsNullOrEmpty(name) && (!string.IsNullOrEmpty(port) || !string.IsNullOrEmpty(dsrouter))
104+
|| !string.IsNullOrEmpty(port) && !string.IsNullOrEmpty(dsrouter))
85105
{
86-
Console.WriteLine("Only one of the --name, --process-id, or --diagnostic-port options may be specified.");
106+
Console.WriteLine("Only one of the --name, --process-id, --diagnostic-port, or --dsrouter options may be specified.");
87107
return false;
88108
}
89-
else if (processId != 0 && !string.IsNullOrEmpty(name))
90-
{
91-
Console.WriteLine("Only one of the --name or --process-id options may be specified.");
92-
return false;
93-
}
94-
else if (processId != 0 && !string.IsNullOrEmpty(port))
95-
{
96-
Console.WriteLine("Only one of the --process-id or --diagnostic-port options may be specified.");
97-
return false;
98-
}
99-
else if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(port))
100-
{
101-
Console.WriteLine("Only one of the --name or --diagnostic-port options may be specified.");
102-
return false;
103-
}
104-
// If we got this far it means only one of --name/--diagnostic-port/--process-id was specified
109+
// If we got this far it means only one of --name/--diagnostic-port/--process-id/--dsrouter was specified
105110
else if (!string.IsNullOrEmpty(port))
106111
{
107112
return true;
108113
}
109114
// Resolve name option
110115
else if (!string.IsNullOrEmpty(name))
111116
{
112-
processId = CommandUtils.FindProcessIdWithName(name);
113-
if (processId < 0)
117+
if ((processId = FindProcessIdWithName(name)) < 0)
114118
{
115119
return false;
116120
}
117121
}
118-
else if (processId == 0)
122+
else if (!string.IsNullOrEmpty(dsrouter))
119123
{
120-
Console.WriteLine("One of the --name, --process-id, or --diagnostic-port options must be specified when attaching to a process.");
121-
return false;
124+
if (dsrouter != "ios" && dsrouter != "android" && dsrouter != "ios-sim" && dsrouter != "android-emu")
125+
{
126+
Console.WriteLine("Invalid value for --dsrouter. Valid values are 'ios', 'ios-sim', 'android' and 'android-emu'.");
127+
return false;
128+
}
129+
if ((processId = LaunchDSRouterProcess(dsrouter)) < 0)
130+
{
131+
if (processId == -2)
132+
{
133+
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.");
134+
}
135+
else
136+
{
137+
Console.WriteLine($"Failed to launch dsrouter: {dsrouter}. Please make sure that dotnet-dsrouter is installed and available in the same directory as dotnet-trace.");
138+
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");
139+
}
140+
return false;
141+
}
122142
}
123143
resolvedProcessId = processId;
124144
return true;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.CommandLine;
6+
using System.CommandLine.Binding;
7+
using System.Diagnostics;
8+
using System.IO;
9+
using System.Text.RegularExpressions;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Microsoft.Extensions.Logging;
13+
using Microsoft.Diagnostics.NETCore.Client;
14+
using Microsoft.Internal.Common;
15+
using Microsoft.Internal.Common.Utils;
16+
17+
namespace Microsoft.Internal.Common.Utils
18+
{
19+
internal sealed partial class DsRouterProcessLauncher
20+
{
21+
private Process _childProc;
22+
private Task _stdOutTask = Task.CompletedTask;
23+
private Task _stdErrTask = Task.CompletedTask;
24+
internal static DsRouterProcessLauncher Launcher = new();
25+
private bool _processStarted;
26+
27+
private static async Task ReadAndIgnoreAllStreamAsync(StreamReader streamToIgnore, CancellationToken cancelToken)
28+
{
29+
Memory<char> memory = new char[4096];
30+
while (await streamToIgnore.ReadAsync(memory, cancelToken).ConfigureAwait(false) != 0)
31+
{
32+
}
33+
}
34+
35+
private bool HasChildProc => _childProc != null;
36+
37+
private Process ChildProc => _childProc;
38+
39+
public int Start(string dsroutercommand, CancellationToken ct)
40+
{
41+
string toolsRoot = System.IO.Path.GetDirectoryName(System.Environment.ProcessPath);
42+
string dotnetDsrouterTool = "dotnet-dsrouter";
43+
44+
if (!string.IsNullOrEmpty(toolsRoot))
45+
{
46+
dotnetDsrouterTool = Path.Combine(toolsRoot, dotnetDsrouterTool);
47+
}
48+
49+
_childProc = new Process();
50+
51+
_childProc.StartInfo.FileName = dotnetDsrouterTool;
52+
_childProc.StartInfo.Arguments = dsroutercommand;
53+
_childProc.StartInfo.UseShellExecute = false;
54+
_childProc.StartInfo.RedirectStandardOutput = true;
55+
_childProc.StartInfo.RedirectStandardError = true;
56+
_childProc.StartInfo.RedirectStandardInput = true;
57+
try
58+
{
59+
_childProc.Start();
60+
_processStarted = true;
61+
}
62+
catch (Exception e)
63+
{
64+
Console.WriteLine($"An error occurred trying to start process '{_childProc.StartInfo.FileName}' with working directory '{System.IO.Directory.GetCurrentDirectory()}'");
65+
Console.WriteLine($"{e.Message}");
66+
return -1;
67+
}
68+
69+
_stdErrTask = ReadAndIgnoreAllStreamAsync(_childProc.StandardError, ct);
70+
_stdOutTask = ReadAndIgnoreAllStreamAsync(_childProc.StandardOutput, ct);
71+
Task.Delay(1000, ct).Wait(ct);
72+
return !_childProc.HasExited ? _childProc.Id : -2;
73+
}
74+
75+
public void Cleanup()
76+
{
77+
if (_childProc != null && _processStarted && !_childProc.HasExited)
78+
{
79+
try
80+
{
81+
_childProc.Kill();
82+
}
83+
// if process exited while we were trying to kill it, it can throw IOE
84+
catch (InvalidOperationException) { }
85+
_stdOutTask.Wait();
86+
_stdErrTask.Wait();
87+
}
88+
}
89+
}
90+
}

src/Tools/dotnet-counters/CounterMonitor.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ public async Task<ReturnCode> Monitor(
176176
int maxHistograms,
177177
int maxTimeSeries,
178178
TimeSpan duration,
179-
bool showDeltas)
179+
bool showDeltas,
180+
string dsrouter)
180181
{
181182
try
182183
{
@@ -186,7 +187,7 @@ public async Task<ReturnCode> Monitor(
186187
// to it.
187188
ValidateNonNegative(maxHistograms, nameof(maxHistograms));
188189
ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries));
189-
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId))
190+
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId))
190191
{
191192
return ReturnCode.ArgumentError;
192193
}
@@ -261,7 +262,8 @@ public async Task<ReturnCode> Collect(
261262
bool resumeRuntime,
262263
int maxHistograms,
263264
int maxTimeSeries,
264-
TimeSpan duration)
265+
TimeSpan duration,
266+
string dsrouter)
265267
{
266268
try
267269
{
@@ -271,7 +273,7 @@ public async Task<ReturnCode> Collect(
271273
// to it.
272274
ValidateNonNegative(maxHistograms, nameof(maxHistograms));
273275
ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries));
274-
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId))
276+
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out _processId))
275277
{
276278
return ReturnCode.ArgumentError;
277279
}

src/Tools/dotnet-counters/Program.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ private static Command MonitorCommand()
5151
maxHistograms: parseResult.GetValue(MaxHistogramOption),
5252
maxTimeSeries: parseResult.GetValue(MaxTimeSeriesOption),
5353
duration: parseResult.GetValue(DurationOption),
54-
showDeltas: parseResult.GetValue(ShowDeltasOption)
54+
showDeltas: parseResult.GetValue(ShowDeltasOption),
55+
dsrouter: string.Empty
5556
));
5657

5758
return monitorCommand;
@@ -92,7 +93,8 @@ private static Command CollectCommand()
9293
resumeRuntime: parseResult.GetValue(ResumeRuntimeOption),
9394
maxHistograms: parseResult.GetValue(MaxHistogramOption),
9495
maxTimeSeries: parseResult.GetValue(MaxTimeSeriesOption),
95-
duration: parseResult.GetValue(DurationOption)));
96+
duration: parseResult.GetValue(DurationOption),
97+
dsrouter: string.Empty));
9698

9799
return collectCommand;
98100
}

src/Tools/dotnet-counters/dotnet-counters.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<Compile Include="..\Common\ProcessNativeMethods\ProcessNativeMethods.cs" Link="ProcessNativeMethods.cs" />
2222
<Compile Include="..\Common\WindowsProcessExtension\WindowsProcessExtension.cs" Link="WindowsProcessExtension.cs" />
2323
<Compile Include="..\Common\CommandLineErrorException.cs" Link="CommandLineErrorException.cs" />
24+
<Compile Include="..\Common\DsRouterProcessLauncher.cs" Link="DsRouterProcessLauncher.cs" />
2425
</ItemGroup>
2526

2627
<ItemGroup>

src/Tools/dotnet-dump/dotnet-dump.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<Compile Include="$(MSBuildThisFileDirectory)..\Common\Commands\Utils.cs" Link="Utils.cs" />
2323
<Compile Include="$(MSBuildThisFileDirectory)..\Common\ProcessNativeMethods\ProcessNativeMethods.cs" Link="ProcessNativeMethods.cs" />
2424
<Compile Include="$(MSBuildThisFileDirectory)..\Common\WindowsProcessExtension\WindowsProcessExtension.cs" Link="WindowsProcessExtension.cs" />
25+
<Compile Include="..\Common\DsRouterProcessLauncher.cs" Link="DsRouterProcessLauncher.cs" />
2526
</ItemGroup>
2627

2728
<ItemGroup>

src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ internal static class CollectCommandHandler
2727
/// <param name="name">The process name to collect the gcdump from.</param>
2828
/// <param name="diagnosticPort">The diagnostic IPC channel to collect the gcdump from.</param>
2929
/// <returns></returns>
30-
private static async Task<int> Collect(CancellationToken ct, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort)
30+
private static async Task<int> Collect(CancellationToken ct, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, string dsrouter)
3131
{
32-
if (!CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out int resolvedProcessId))
32+
if (!CommandUtils.ResolveProcessForAttach(processId, name, diagnosticPort, dsrouter, out int resolvedProcessId))
3333
{
3434
return -1;
3535
}
@@ -148,7 +148,8 @@ public static Command CollectCommand()
148148
timeout: parseResult.GetValue(TimeoutOption),
149149
verbose: parseResult.GetValue(VerboseOption),
150150
name: parseResult.GetValue(NameOption),
151-
diagnosticPort: parseResult.GetValue(DiagnosticPortOption) ?? string.Empty));
151+
diagnosticPort: parseResult.GetValue(DiagnosticPortOption) ?? string.Empty,
152+
dsrouter: string.Empty));
152153

153154
return collectCommand;
154155
}

src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ private static Task<int> Report(CancellationToken ct, FileInfo gcdump_filename,
7777

7878
return (source, type) switch
7979
{
80-
(ReportSource.Process, ReportType.HeapStat) => ReportFromProcess(processId ?? 0, diagnosticPort, ct),
80+
(ReportSource.Process, ReportType.HeapStat) => ReportFromProcess(processId ?? 0, diagnosticPort, dsrouter: string.Empty, ct: ct),
8181
(ReportSource.DumpFile, ReportType.HeapStat) => ReportFromFile(gcdump_filename),
8282
_ => HandleUnknownParam()
8383
};
@@ -89,9 +89,9 @@ private static Task<int> HandleUnknownParam()
8989
return Task.FromResult(-1);
9090
}
9191

92-
private static Task<int> ReportFromProcess(int processId, string diagnosticPort, CancellationToken ct)
92+
private static Task<int> ReportFromProcess(int processId, string diagnosticPort, string dsrouter, CancellationToken ct)
9393
{
94-
if (!CommandUtils.ValidateArgumentsForAttach(processId, string.Empty, diagnosticPort, out int resolvedProcessId))
94+
if (!CommandUtils.ResolveProcessForAttach(processId, string.Empty, diagnosticPort, dsrouter, out int resolvedProcessId))
9595
{
9696
return Task.FromResult(-1);
9797
}

src/Tools/dotnet-gcdump/dotnet-gcdump.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<Compile Include="..\Common\Commands\Utils.cs" Link="Utils.cs" />
2727
<Compile Include="..\Common\ProcessNativeMethods\ProcessNativeMethods.cs" Link="ProcessNativeMethods.cs" />
2828
<Compile Include="..\Common\WindowsProcessExtension\WindowsProcessExtension.cs" Link="WindowsProcessExtension.cs" />
29+
<Compile Include="..\Common\DsRouterProcessLauncher.cs" Link="DsRouterProcessLauncher.cs" />
2930
</ItemGroup>
3031

3132
</Project>

src/Tools/dotnet-stack/dotnet-stack.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<Compile Include="..\Common\Commands\Utils.cs" Link="Utils.cs" />
2626
<Compile Include="..\Common\ProcessNativeMethods\ProcessNativeMethods.cs" Link="ProcessNativeMethods.cs" />
2727
<Compile Include="..\Common\WindowsProcessExtension\WindowsProcessExtension.cs" Link="WindowsProcessExtension.cs" />
28+
<Compile Include="..\Common\DsRouterProcessLauncher.cs" Link="DsRouterProcessLauncher.cs" />
2829
</ItemGroup>
2930

3031
<ItemGroup>

0 commit comments

Comments
 (0)