Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
155b9af
[dotnet-counters] Revert CounterMonitor to using IConsole
mdh1418 Oct 28, 2025
079b98a
[dotnet-trace][collect] Set console through constructor
mdh1418 Oct 28, 2025
3422ff6
[CommandUtils] Enable output redirection
mdh1418 Oct 29, 2025
30d433d
Revert "[CommandUtils] Enable output redirection"
mdh1418 Oct 29, 2025
b116e70
[CommandUtils] Convert FindProcessIdWithName to throw CommandLineErro…
mdh1418 Oct 29, 2025
44f7364
[CommandUtils] Convert ValidateArgumentsForChildProcess to throw
mdh1418 Oct 29, 2025
7a8450a
[CommandUtils] Convert ResolveProcessForAttach to throw
mdh1418 Oct 29, 2025
2e9cab0
Add corresponding compile items
mdh1418 Oct 29, 2025
f7f7704
Fix exception typo
mdh1418 Oct 29, 2025
90f7841
Default DefaultConsole to not use ansi
mdh1418 Oct 29, 2025
7b0aa79
[dotnet-trace] Fix ReturnCode from CommandLineErrorException
mdh1418 Oct 29, 2025
6be25ae
[dotnet-trace] Handle DSRouter Launch Failure ReturnCode
mdh1418 Oct 29, 2025
0e29459
Cleanup ProcessLauncher Start
mdh1418 Oct 29, 2025
e2d336a
Rename CommandLineErrorException
mdh1418 Oct 30, 2025
61d6413
Merge remote-tracking branch 'upstream/main' into enable_command_util…
mdh1418 Oct 31, 2025
2814b51
[CommandUtils] Convert ResolveProcess to throw
mdh1418 Oct 31, 2025
bdeb602
Add ReturnCode to DiagnosticToolException
mdh1418 Nov 4, 2025
cced459
[ToolsCommon] Break out ReturnCode
mdh1418 Nov 5, 2025
56d7451
[ToolsCommon] Break out LineRewriter
mdh1418 Nov 5, 2025
944c6ce
[CommonTools] Rename and move CommandUtils
mdh1418 Nov 5, 2025
f35c402
Cleanup remnant cast
mdh1418 Nov 6, 2025
0184d7c
Merge remote-tracking branch 'upstream/main' into enable_command_util…
mdh1418 Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand All @@ -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}.");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tools.Common;
using Microsoft.Diagnostics.Tools;

namespace Microsoft.Internal.Common.Utils
{
Expand All @@ -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 DiagnosticToolException($"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 DiagnosticToolException($"There is no active process with the given name: {name}");
}
return commonId;
}
Expand All @@ -61,45 +60,39 @@ public static int LaunchDSRouterProcess(string dsrouterCommand)
/// <param name="name">name</param>
/// <param name="port">port</param>
/// <returns></returns>
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 DiagnosticToolException("None of the --name, --process-id, or --diagnostic-port options may be specified when launching a child process.");
}

return true;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="processId">process ID</param>
/// <param name="name">name</param>
/// <param name="resolvedProcessId">resolvedProcessId</param>
/// <param name="resolvedProcessName">resolvedProcessName</param>
/// <returns></returns>
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
{
Expand All @@ -116,159 +109,70 @@ 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;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="processId">process ID</param>
/// <param name="name">name</param>
/// <param name="port">port</param>
/// <param name="dsrouter">dsrouter</param>
/// <param name="resolvedProcessId">resolvedProcessId</param>
/// <returns></returns>
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 DiagnosticToolException("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 DiagnosticToolException($"{processId} is not a valid process ID");
}
else if ((processId != 0 ? 1 : 0) +
(!string.IsNullOrEmpty(name) ? 1 : 0) +
(!string.IsNullOrEmpty(port) ? 1 : 0) +
(!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 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))
{
return true;
return;
}
// Resolve name option
else if (!string.IsNullOrEmpty(name))
{
if ((processId = FindProcessIdWithName(name)) < 0)
{
return false;
}
processId = FindProcessIdWithName(name);
}
else if (!string.IsNullOrEmpty(dsrouter))
{
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 DiagnosticToolException("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 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
{
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 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", ReturnCode.TracingError);
}
return false;
}
}
resolvedProcessId = processId;
return true;
}
}

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;
}
}
}
}

internal enum ReturnCode
{
Ok,
SessionCreationError,
TracingError,
ArgumentError,
PlatformNotSupportedError,
UnknownError
}
}
2 changes: 1 addition & 1 deletion src/Tools/Common/DefaultConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -16,8 +17,12 @@ 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 ReturnCode ReturnCode { get; }
public DiagnosticToolException(string errorMessage, ReturnCode returnCode = ReturnCode.ArgumentError ) : base(errorMessage)
{
ReturnCode = returnCode;
}
}
}
74 changes: 74 additions & 0 deletions src/Tools/Common/LineRewriter.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
}
15 changes: 15 additions & 0 deletions src/Tools/Common/ReturnCode.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading