Upgrade to latest System.CommandLine version (#668)

Upgrade to latest System.CommandLine version

Use collection initializers
This commit is contained in:
Mike McLaughlin 2019-12-06 15:13:16 -08:00 коммит произвёл GitHub
Родитель 7df57cc8b9
Коммит 125ea40662
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
27 изменённых файлов: 302 добавлений и 376 удалений

Просмотреть файл

@ -63,6 +63,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tracee", "src\tests\Tracee\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventPipe.UnitTests", "src\tests\eventpipe\EventPipe.UnitTests.csproj", "{CED9ABBA-861E-4C0A-9359-22351208EF27}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{298AE119-6625-4604-BDE5-0765DC34C856}"
ProjectSection(SolutionItems) = preProject
src\Tools\Common\CommandExtensions.cs = src\Tools\Common\CommandExtensions.cs
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Commands", "Commands", "{C457CBCD-3A8D-4402-9A2B-693A0390D3F9}"
ProjectSection(SolutionItems) = preProject
src\Tools\Common\Commands\ProcessStatus.cs = src\Tools\Common\Commands\ProcessStatus.cs
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Checked|Any CPU = Checked|Any CPU
@ -1090,6 +1100,8 @@ Global
{6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{C79D6069-2C18-48CB-846E-71F7168C2F7D} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{CED9ABBA-861E-4C0A-9359-22351208EF27} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{298AE119-6625-4604-BDE5-0765DC34C856} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{C457CBCD-3A8D-4402-9A2B-693A0390D3F9} = {298AE119-6625-4604-BDE5-0765DC34C856}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}

Просмотреть файл

@ -25,8 +25,8 @@
<MicrosoftDiagnosticsRuntimeVersion>1.1.46104</MicrosoftDiagnosticsRuntimeVersion>
<MicrosoftDiaSymReaderNativePackageVersion>1.7.0</MicrosoftDiaSymReaderNativePackageVersion>
<MicrosoftDiagnosticsTracingTraceEventVersion>2.0.44</MicrosoftDiagnosticsTracingTraceEventVersion>
<SystemCommandLineExperimentalVersion>0.2.0-alpha.19254.1</SystemCommandLineExperimentalVersion>
<SystemCommandLineRenderingVersion>0.2.0-alpha.19254.1</SystemCommandLineRenderingVersion>
<SystemCommandLineExperimentalVersion>0.3.0-alpha.19602.1</SystemCommandLineExperimentalVersion>
<SystemCommandLineRenderingVersion>0.3.0-alpha.19602.1</SystemCommandLineRenderingVersion>
<SystemMemoryVersion>4.5.3</SystemMemoryVersion>
<XUnitVersion>2.4.1</XUnitVersion>
<XUnitAbstractionsVersion>2.0.3</XUnitAbstractionsVersion>

Просмотреть файл

@ -118,32 +118,32 @@ namespace Microsoft.Diagnostics.Repl
{
command = new Command(commandAttribute.Name, commandAttribute.Help);
var properties = new List<(PropertyInfo, Option)>();
PropertyInfo argument = null;
var arguments = new List<(PropertyInfo, Argument)>();
foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
{
var argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
if (argumentAttribute != null)
{
if (argument != null) {
throw new ArgumentException($"More than one ArgumentAttribute in command class: {type.Name}");
}
IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
command.Argument = new Argument {
var argument = new Argument {
Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
Description = argumentAttribute.Help,
ArgumentType = property.PropertyType,
Arity = arity
};
argument = property;
command.AddArgument(argument);
arguments.Add((property, argument));
}
else
{
var optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
if (optionAttribute != null)
{
var option = new Option(optionAttribute.Name ?? BuildAlias(property.Name), optionAttribute.Help, new Argument { ArgumentType = property.PropertyType });
var option = new Option(optionAttribute.Name ?? BuildAlias(property.Name), optionAttribute.Help) {
Argument = new Argument { ArgumentType = property.PropertyType }
};
command.AddOption(option);
properties.Add((property, option));
@ -160,7 +160,7 @@ namespace Microsoft.Diagnostics.Repl
}
}
var handler = new Handler(this, commandAttribute.AliasExpansion, argument, properties, type);
var handler = new Handler(this, commandAttribute.AliasExpansion, arguments, properties, type);
_commandHandlers.Add(command.Name, handler);
command.Handler = handler;
@ -201,18 +201,18 @@ namespace Microsoft.Diagnostics.Repl
{
private readonly CommandProcessor _commandProcessor;
private readonly string _aliasExpansion;
private readonly PropertyInfo _argument;
private readonly IEnumerable<(PropertyInfo Property, Argument Argument)> _arguments;
private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties;
private readonly ConstructorInfo _constructor;
private readonly MethodInfo _methodInfo;
private readonly MethodInfo _methodInfoHelp;
public Handler(CommandProcessor commandProcessor, string aliasExpansion, PropertyInfo argument, IEnumerable<(PropertyInfo, Option)> properties, Type type)
public Handler(CommandProcessor commandProcessor, string aliasExpansion, IEnumerable<(PropertyInfo, Argument)> arguments, IEnumerable<(PropertyInfo, Option)> properties, Type type)
{
_commandProcessor = commandProcessor;
_aliasExpansion = aliasExpansion;
_argument = argument;
_arguments = arguments;
_properties = properties;
_constructor = type.GetConstructors().SingleOrDefault((info) => info.GetParameters().Length == 0) ??
@ -303,19 +303,19 @@ namespace Microsoft.Diagnostics.Repl
property.Property.SetValue(instance, value);
}
if (context != null && _argument != null)
if (context != null)
{
object value = null;
ArgumentResult result = context.ParseResult.CommandResult.ArgumentResult;
switch (result)
IEnumerable<ArgumentResult> argumentResults = context.ParseResult.CommandResult.Children.OfType<ArgumentResult>();
foreach ((PropertyInfo Property, Argument Argument) argument in _arguments)
{
case SuccessfulArgumentResult successful:
value = successful.Value;
break;
case FailedArgumentResult failed:
throw new InvalidOperationException(failed.ErrorMessage);
ArgumentResult argumentResult = argumentResults.Where((result) => result.Argument == argument.Argument).SingleOrDefault();
if (argumentResult != null)
{
object value = argumentResult.GetValueOrDefault();
argument.Property.SetValue(instance, value);
}
}
_argument.SetValue(instance, value);
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.CommandLine;
using System.CommandLine.Invocation;
namespace Microsoft.Tools.Common
{
public static class CommandExtenions
{
/// <summary>
/// Allows the command handler to be included in the collection initializer.
/// </summary>
public static void Add(this Command command, ICommandHandler handler)
{
command.Handler = handler;
}
}
}

Просмотреть файл

@ -1,28 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.Serialization;
namespace Microsoft.Internal.Utilities
{
public class CommandLineException : Exception
{
public CommandLineException()
{
}
public CommandLineException(string message) : base(message)
{
}
public CommandLineException(string message, Exception innerException) : base(message, innerException)
{
}
protected CommandLineException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

Просмотреть файл

@ -2,18 +2,24 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.CommandLine;
using System.Diagnostics;
using System.CommandLine.Invocation;
using System.Linq;
using System.Text;
using Microsoft.Diagnostics.NETCore.Client;
using Process = System.Diagnostics.Process;
namespace Microsoft.Internal.Common.Commands
{
public class ProcessStatusCommandHandler
{
public static Command ProcessStatusCommand(string description) =>
new Command(name: "ps", description)
{
Handler = CommandHandler.Create<IConsole>(PrintProcessStatus)
};
/// <summary>
/// Print the current list of available .NET core processes for diagnosis and their statuses
/// </summary>

Просмотреть файл

@ -1,45 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils;
namespace Microsoft.Internal.Utilities
{
public static class ConsoleCancellationExtensions
{
public static CancellationToken GetCtrlCToken(this IConsole console)
{
var cts = new CancellationTokenSource();
console.CancelKeyPress += (sender, args) =>
{
if (cts.IsCancellationRequested)
{
// Terminate forcibly, the user pressed Ctrl-C a second time
args.Cancel = false;
}
else
{
// Don't terminate, just trip the token
args.Cancel = true;
cts.Cancel();
}
};
return cts.Token;
}
public static Task WaitForCtrlCAsync(this IConsole console)
{
var tcs = new TaskCompletionSource<object>();
console.CancelKeyPress += (sender, args) =>
{
// Don't terminate, just trip the task
args.Cancel = true;
tcs.TrySetResult(null);
};
return tcs.Task;
}
}
}

Просмотреть файл

@ -1,25 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Linq;
namespace Microsoft.Internal.Utilities
{
internal static class DebugUtil
{
[Conditional("DEBUG")]
public static void WaitForDebuggerIfRequested(ref string[] args)
{
if (args.Any(a => a == "--debug"))
{
args = args.Where(a => a != "--debug").ToArray();
Console.WriteLine($"Ready for debugger to attach. Process ID: {Process.GetCurrentProcess().Id}.");
Console.WriteLine("Press ENTER to continue.");
Console.ReadLine();
}
}
}
}

Просмотреть файл

@ -2,7 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Internal.Common.Commands;
using Microsoft.Tools.Common;
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Binding;
using System.CommandLine.Builder;
@ -10,9 +14,6 @@ using System.CommandLine.Invocation;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Internal.Common.Commands;
namespace Microsoft.Diagnostics.Tools.Counters
{
@ -24,67 +25,72 @@ namespace Microsoft.Diagnostics.Tools.Counters
private static Command MonitorCommand() =>
new Command(
"monitor",
"Start monitoring a .NET application",
new Option[] { ProcessIdOption(), RefreshIntervalOption() },
argument: CounterList(),
handler: CommandHandler.Create<CancellationToken, List<string>, IConsole, int, int>(new CounterMonitor().Monitor));
name: "monitor",
description: "Start monitoring a .NET application")
{
// Handler
CommandHandler.Create<CancellationToken, List<string>, IConsole, int, int>(new CounterMonitor().Monitor),
// Arguments and Options
CounterList(), ProcessIdOption(), RefreshIntervalOption()
};
private static Command CollectCommand() =>
new Command(
"collect",
"Monitor counters in a .NET application and export the result into a file",
new Option[] { ProcessIdOption(), RefreshIntervalOption(), ExportFormatOption(), ExportFileNameOption() },
argument: CounterList(),
handler: HandlerDescriptor.FromDelegate((ExportDelegate)new CounterMonitor().Collect).GetCommandHandler());
name: "collect",
description: "Monitor counters in a .NET application and export the result into a file")
{
// Handler
HandlerDescriptor.FromDelegate((ExportDelegate)new CounterMonitor().Collect).GetCommandHandler(),
// Arguments and Options
CounterList(), ProcessIdOption(), RefreshIntervalOption(), ExportFormatOption(), ExportFileNameOption()
};
private static Option ProcessIdOption() =>
new Option(
new[] { "-p", "--process-id" },
"The ID of the process that will be monitored.",
new Argument<int> { Name = "pid" });
aliases: new[] { "-p", "--process-id" },
description: "The process id that will be monitored.")
{
Argument = new Argument<int>(name: "pid")
};
private static Option RefreshIntervalOption() =>
new Option(
new[] { "--refresh-interval" },
"The number of seconds to delay between updating the displayed counters.",
new Argument<int>(defaultValue: 1) { Name = "refresh-interval" });
alias: "--refresh-interval",
description: "The number of seconds to delay between updating the displayed counters.")
{
Argument = new Argument<int>(name: "refresh-interval", defaultValue: 1)
};
private static Option ExportFormatOption() =>
private static Option ExportFormatOption() =>
new Option(
new[] { "--format" },
"The format of exported counter data.",
new Argument<CountersExportFormat>(defaultValue: CountersExportFormat.csv) { Name = "format" });
alias: "--format",
description: "The format of exported counter data.")
{
Argument = new Argument<CountersExportFormat>(name: "format", defaultValue: CountersExportFormat.csv)
};
private static Option ExportFileNameOption() =>
private static Option ExportFileNameOption() =>
new Option(
new[] { "-o", "--output" },
"The output file name.",
new Argument<string>(defaultValue: "counter") { Name = "output" });
aliases: new[] { "-o", "--output" },
description: "The output file name.")
{
Argument = new Argument<string>(name: "output", defaultValue: "counter")
};
private static Argument CounterList() =>
new Argument<List<string>> {
Name = "counter_list",
Description = @"A space separated list of counters. Counters can be specified provider_name[:counter_name].
If the provider_name is used without a qualifying counter_name then all counters will be shown. To discover
provider and counter names, use the list command.
.",
new Argument<List<string>>(name: "counter_list", defaultValue: new List<string>())
{
Description = @"A space separated list of counters. Counters can be specified provider_name[:counter_name]. If the provider_name is used without a qualifying counter_name then all counters will be shown. To discover provider and counter names, use the list command.",
Arity = ArgumentArity.ZeroOrMore
};
private static Command ListCommand() =>
new Command(
"list",
"Display a list of counter names and descriptions, grouped by provider.",
new Option[] { },
handler: CommandHandler.Create<IConsole>(List));
private static Command ProcessStatusCommand() =>
new Command(
"ps",
"Display a list of dotnet processes that can be monitored.",
new Option[] { },
handler: CommandHandler.Create<IConsole>(ProcessStatusCommandHandler.PrintProcessStatus));
name: "list",
description: "Display a list of counter names and descriptions, grouped by provider.")
{
Handler = CommandHandler.Create<IConsole>(List)
};
public static int List(IConsole console)
{
@ -111,7 +117,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
.AddCommand(MonitorCommand())
.AddCommand(CollectCommand())
.AddCommand(ListCommand())
.AddCommand(ProcessStatusCommand())
.AddCommand(ProcessStatusCommandHandler.ProcessStatusCommand("Lists the dotnet processes that can be monitored"))
.UseDefaults()
.Build();
return parser.InvokeAsync(args);

Просмотреть файл

@ -12,6 +12,7 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)..\dotnet-trace\Extensions.cs" Link="Extensions.cs" />
<Compile Include="..\Common\CommandExtensions.cs" Link="CommandExtensions.cs" />
<Compile Include="..\Common\Commands\ProcessStatus.cs" Link="ProcessStatus.cs" />
</ItemGroup>

Просмотреть файл

@ -76,7 +76,8 @@ namespace Microsoft.Diagnostics.Tools.Dump
// Run the commands from the dotnet-dump command line
if (command != null)
{
foreach (string cmd in command) {
foreach (string cmd in command)
{
await _commandProcessor.Parse(cmd);
}
}

Просмотреть файл

@ -59,7 +59,7 @@ namespace Microsoft.Diagnostics.Tools.Dump
{
try {
string arguments = null;
if (Arguments.Length > 0) {
if (Arguments != null && Arguments.Length > 0) {
arguments = string.Concat(Arguments.Select((arg) => arg + " "));
}
SOSHost.ExecuteCommand(AliasExpansion, arguments);

Просмотреть файл

@ -2,13 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Internal.Common.Commands;
using Microsoft.Tools.Common;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Invocation;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Internal.Common.Commands;
namespace Microsoft.Diagnostics.Tools.Dump
{
@ -19,7 +20,7 @@ namespace Microsoft.Diagnostics.Tools.Dump
var parser = new CommandLineBuilder()
.AddCommand(CollectCommand())
.AddCommand(AnalyzeCommand())
.AddCommand(ProcessStatusCommand())
.AddCommand(ProcessStatusCommandHandler.ProcessStatusCommand("Lists the dotnet processes that dumps can be collected"))
.UseDefaults()
.Build();
@ -27,67 +28,74 @@ namespace Microsoft.Diagnostics.Tools.Dump
}
private static Command CollectCommand() =>
new Command(
"collect",
"Capture dumps from a process",
new Option[] { ProcessIdOption(), OutputOption(), DiagnosticLoggingOption(), TypeOption() },
handler: CommandHandler.Create<IConsole, int, string, bool, Dumper.DumpTypeOption>(new Dumper().Collect));
new Command( name: "collect", description: "Capture dumps from a process")
{
// Handler
CommandHandler.Create<IConsole, int, string, bool, Dumper.DumpTypeOption>(new Dumper().Collect),
// Options
ProcessIdOption(), OutputOption(), DiagnosticLoggingOption(), TypeOption()
};
private static Option ProcessIdOption() =>
new Option(
new[] { "-p", "--process-id" },
"The process to collect a memory dump from.",
new Argument<int> { Name = "pid" });
aliases: new[] { "-p", "--process-id" },
description: "The process id to collect a memory dump.")
{
Argument = new Argument<int>(name: "pid")
};
private static Option OutputOption() =>
new Option(
new[] { "-o", "--output" },
@"The path where collected dumps should be written. Defaults to '.\dump_YYYYMMDD_HHMMSS.dmp' on Windows and
'./core_YYYYMMDD_HHMMSS' on Linux where YYYYMMDD is Year/Month/Day and HHMMSS is Hour/Minute/Second. Otherwise, it is the full
path and file name of the dump.",
new Argument<string>() { Name = "output_dump_path" });
new Option(
aliases: new[] { "-o", "--output" },
description: @"The path where collected dumps should be written. Defaults to '.\dump_YYYYMMDD_HHMMSS.dmp' on Windows and './core_YYYYMMDD_HHMMSS'
on Linux where YYYYMMDD is Year/Month/Day and HHMMSS is Hour/Minute/Second. Otherwise, it is the full path and file name of the dump.")
{
Argument = new Argument<string>(name: "output_dump_path")
};
private static Option DiagnosticLoggingOption() =>
new Option(
new[] { "--diag" },
"Enable dump collection diagnostic logging.",
new Argument<bool> { Name = "diag" });
alias: "--diag",
description: "Enable dump collection diagnostic logging.")
{
Argument = new Argument<bool>(name: "diag")
};
private static Option TypeOption() =>
new Option(
"--type",
@"The dump type determines the kinds of information that are collected from the process. There are two types:
heap - A large and relatively comprehensive dump containing module lists, thread lists, all stacks,
exception information, handle information, and all memory except for mapped images.
mini - A small dump containing module lists, thread lists, exception information and all stacks.
If not specified 'heap' is the default.",
new Argument<Dumper.DumpTypeOption>(Dumper.DumpTypeOption.Heap) { Name = "dump_type" });
alias: "--type",
description: @"The dump type determines the kinds of information that are collected from the process. There are two types: heap - A large and
relatively comprehensive dump containing module lists, thread lists, all stacks, exception information, handle information, and all memory except for mapped
images. mini - A small dump containing module lists, thread lists, exception information and all stacks. If not specified 'heap' is the default.")
{
Argument = new Argument<Dumper.DumpTypeOption>(name: "dump_type", defaultValue: Dumper.DumpTypeOption.Heap)
};
private static Command AnalyzeCommand() =>
new Command(
"analyze",
"Starts an interactive shell with debugging commands to explore a dump",
new Option[] { RunCommand() }, argument: DumpPath(),
handler: CommandHandler.Create<FileInfo, string[]>(new Analyzer().Analyze));
name: "analyze",
description: "Starts an interactive shell with debugging commands to explore a dump")
{
// Handler
CommandHandler.Create<FileInfo, string[]>(new Analyzer().Analyze),
// Arguments and Options
DumpPath(),
RunCommand()
};
private static Argument DumpPath() =>
new Argument<FileInfo> {
Name = "dump_path",
Description = "Name of the dump file to analyze." }.ExistingOnly();
new Argument<FileInfo>(
name: "dump_path")
{
Description = "Name of the dump file to analyze."
}.ExistingOnly();
private static Option RunCommand() =>
new Option(
new[] { "-c", "--command" },
"Run the command on start.",
new Argument<string[]>() { Name = "command", Arity = ArgumentArity.ZeroOrMore });
private static Command ProcessStatusCommand() =>
new Command(
"ps",
"Display a list of dotnet processes to create dump from",
new Option[] { },
handler: CommandHandler.Create<IConsole>(ProcessStatusCommandHandler.PrintProcessStatus));
aliases: new[] { "-c", "--command" },
description: "Run the command on start.")
{
Argument = new Argument<string[]>(name: "command", defaultValue: new string[0]) { Arity = ArgumentArity.ZeroOrMore }
};
}
}

Просмотреть файл

@ -18,6 +18,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\Common\CommandExtensions.cs" Link="CommandExtensions.cs" />
<Compile Include="..\Common\Commands\ProcessStatus.cs" Link="ProcessStatus.cs" />
</ItemGroup>

Просмотреть файл

@ -2,14 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Tools.Common;
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Binding;
using System.CommandLine.Rendering;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -100,42 +97,45 @@ namespace Microsoft.Diagnostics.Tools.GCDump
public static Command CollectCommand() =>
new Command(
name: "collect",
description: "Collects a diagnostic trace from a currently running process",
symbols: new Option[] {
ProcessIdOption(),
OutputPathOption(),
VerboseOption(),
TimeoutOption()
},
handler: HandlerDescriptor.FromDelegate((CollectDelegate)Collect).GetCommandHandler());
description: "Collects a diagnostic trace from a currently running process")
{
// Handler
HandlerDescriptor.FromDelegate((CollectDelegate)Collect).GetCommandHandler(),
// Options
ProcessIdOption(), OutputPathOption(), VerboseOption(), TimeoutOption()
};
public static Option ProcessIdOption() =>
new Option(
aliases: new[] { "-p", "--process-id" },
description: "The process to collect the trace from",
argument: new Argument<int>(defaultValue: 0) { Name = "pid" },
isHidden: false);
description: "The process id to collect the trace.")
{
Argument = new Argument<int>(name: "pid", defaultValue: 0),
};
private static Option OutputPathOption() =>
new Option(
aliases: new[] { "-o", "--output" },
description: $@"The path where collected gcdumps should be written. Defaults to '.\YYYYMMDD_HHMMSS_<pid>.gcdump' where YYYYMMDD is Year/Month/Day and HHMMSS is Hour/Minute/Second. Otherwise, it is the full path and file name of the dump.",
argument: new Argument<string>(defaultValue: "") { Name = "gcdump-file-path" },
isHidden: false);
description: $@"The path where collected gcdumps should be written. Defaults to '.\YYYYMMDD_HHMMSS_<pid>.gcdump' where YYYYMMDD is Year/Month/Day and HHMMSS is Hour/Minute/Second. Otherwise, it is the full path and file name of the dump.")
{
Argument = new Argument<string>(name: "gcdump-file-path", defaultValue: "")
};
private static Option VerboseOption() =>
new Option(
aliases: new[] { "-v", "--verbose" },
description: $"Output the log while collecting the gcdump",
argument: new Argument<bool>(defaultValue: false) { Name = "verbose" },
isHidden: false);
description: $"Output the log while collecting the gcdump.")
{
Argument = new Argument<bool>(name: "verbose", defaultValue: false)
};
private static int DefaultTimeout = 30;
private static Option TimeoutOption() =>
new Option(
aliases: new[] { "-t", "--timeout" },
description: $"Give up on collecting the gcdump if it takes longer than this many seconds. The default value is {DefaultTimeout}s",
argument: new Argument<int>(defaultValue: DefaultTimeout) { Name = "timeout" },
isHidden: false);
description: $"Give up on collecting the gcdump if it takes longer than this many seconds. The default value is {DefaultTimeout}s.")
{
Argument = new Argument<int>(name: "timeout", defaultValue: DefaultTimeout)
};
}
}

Просмотреть файл

@ -1,28 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Internal.Common.Commands;
using System;
using System.CommandLine;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.Tools.GCDump
{
internal static class ListProcessesCommandHandler
{
public static async Task<int> GetActivePorts(IConsole console)
{
ProcessStatusCommandHandler.PrintProcessStatus(console);
await Task.FromResult(0);
return 0;
}
public static Command ProcessStatusCommand() =>
new Command(
name: "ps",
description: "Lists dotnet processes that can be attached to.",
handler: System.CommandLine.Invocation.CommandHandler.Create<IConsole>(GetActivePorts),
isHidden: false);
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Internal.Common.Commands;
using System.CommandLine.Builder;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
@ -14,7 +15,7 @@ namespace Microsoft.Diagnostics.Tools.GCDump
{
var parser = new CommandLineBuilder()
.AddCommand(CollectCommandHandler.CollectCommand())
.AddCommand(ListProcessesCommandHandler.ProcessStatusCommand())
.AddCommand(ProcessStatusCommandHandler.ProcessStatusCommand("Lists the dotnet processes that gcdumps can be collected"))
.UseDefaults()
.Build();

Просмотреть файл

@ -21,6 +21,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\Common\CommandExtensions.cs" Link="CommandExtensions.cs" />
<Compile Include="..\Common\Commands\ProcessStatus.cs" Link="ProcessStatus.cs" />
</ItemGroup>

Просмотреть файл

@ -2,12 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Tools.Common;
using SOS;
using System;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Invocation;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
@ -28,22 +27,30 @@ namespace Microsoft.Diagnostics.Tools.SOS
private static Command InstallCommand() =>
new Command(
"install",
"Installs SOS and configures LLDB to load it on startup.",
new Option[] { ArchitectureOption() },
handler: CommandHandler.Create<IConsole, Architecture?>((console, architecture) => InvokeAsync(console, architecture, install: true)));
name: "install",
description: "Installs SOS and configures LLDB to load it on startup.")
{
// Handler
CommandHandler.Create<IConsole, Architecture?>((console, architecture) => InvokeAsync(console, architecture, install: true)),
// Options
ArchitectureOption()
};
private static Option ArchitectureOption() =>
new Option(
new[] { "--architecture" },
"The process to collect a memory dump from.",
new Argument<Architecture>() { Name = "architecture" });
alias: "--architecture",
description: "The processor architecture to install.")
{
Argument = new Argument<Architecture>(name: "architecture")
};
private static Command UninstallCommand() =>
new Command(
"uninstall",
"Uninstalls SOS and reverts any configuration changes to LLDB.",
handler: CommandHandler.Create<IConsole>((console) => InvokeAsync(console, architecture: null, install: false)));
name: "uninstall",
description: "Uninstalls SOS and reverts any configuration changes to LLDB.")
{
Handler = CommandHandler.Create<IConsole>((console) => InvokeAsync(console, architecture: null, install: false))
};
private static Task<int> InvokeAsync(IConsole console, Architecture? architecture, bool install)
{

Просмотреть файл

@ -9,6 +9,9 @@
<PackageReleaseNotes>$(Description)</PackageReleaseNotes>
<SOSPackagePathPrefix>tools/$(TargetFramework)/any</SOSPackagePathPrefix>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\CommandExtensions.cs" Link="CommandExtensions.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\SOS\SOS.InstallHelper\SOS.InstallHelper.csproj" />

Просмотреть файл

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Tools.Common;
using System;
using System.Collections.Generic;
using System.CommandLine;
@ -261,55 +262,63 @@ namespace Microsoft.Diagnostics.Tools.Trace
public static Command CollectCommand() =>
new Command(
name: "collect",
description: "Collects a diagnostic trace from a currently running process",
symbols: new Option[] {
CommonOptions.ProcessIdOption(),
CircularBufferOption(),
OutputPathOption(),
ProvidersOption(),
ProfileOption(),
CommonOptions.FormatOption(),
DurationOption()
},
handler: HandlerDescriptor.FromDelegate((CollectDelegate)Collect).GetCommandHandler());
description: "Collects a diagnostic trace from a currently running process")
{
// Handler
HandlerDescriptor.FromDelegate((CollectDelegate)Collect).GetCommandHandler(),
// Options
CommonOptions.ProcessIdOption(),
CircularBufferOption(),
OutputPathOption(),
ProvidersOption(),
ProfileOption(),
CommonOptions.FormatOption(),
DurationOption()
};
private static uint DefaultCircularBufferSizeInMB => 256;
private static Option CircularBufferOption() =>
new Option(
alias: "--buffersize",
description: $"Sets the size of the in-memory circular buffer in megabytes. Default {DefaultCircularBufferSizeInMB} MB.",
argument: new Argument<uint>(defaultValue: DefaultCircularBufferSizeInMB) { Name = "size" },
isHidden: false);
description: $"Sets the size of the in-memory circular buffer in megabytes. Default {DefaultCircularBufferSizeInMB} MB.")
{
Argument = new Argument<uint>(name: "size", defaultValue: DefaultCircularBufferSizeInMB)
};
public static string DefaultTraceName => "trace.nettrace";
private static Option OutputPathOption() =>
new Option(
aliases: new[] { "-o", "--output" },
description: $"The output path for the collected trace data. If not specified it defaults to '{DefaultTraceName}'",
argument: new Argument<FileInfo>(defaultValue: new FileInfo(DefaultTraceName)) { Name = "trace-file-path" },
isHidden: false);
description: $"The output path for the collected trace data. If not specified it defaults to '{DefaultTraceName}'.")
{
Argument = new Argument<FileInfo>(name: "trace-file-path", defaultValue: new FileInfo(DefaultTraceName))
};
private static Option ProvidersOption() =>
new Option(
alias: "--providers",
description: @"A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: 'KnownProviderName[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'. These providers are in addition to any providers implied by the --profile argument. If there is any discrepancy for a particular provider, the configuration here takes precedence over the implicit configuration from the profile.",
argument: new Argument<string>(defaultValue: "") { Name = "list-of-comma-separated-providers" }, // TODO: Can we specify an actual type?
isHidden: false);
description: @"A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: 'KnownProviderName[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'. These providers are in addition to any providers implied by the --profile argument. If there is any discrepancy for a particular provider, the configuration here takes precedence over the implicit configuration from the profile.")
{
Argument = new Argument<string>(name: "list-of-comma-separated-providers", defaultValue: "") // TODO: Can we specify an actual type?
};
private static Option ProfileOption() =>
new Option(
alias: "--profile",
description: @"A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.",
argument: new Argument<string>(defaultValue: "") { Name = "profile-name" },
isHidden: false);
description: @"A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.")
{
Argument = new Argument<string>(name: "profile-name", defaultValue: "")
};
private static Option DurationOption() =>
new Option(
alias: "--duration",
description: @"When specified, will trace for the given timespan and then automatically stop the trace. Provided in the form of dd:hh:mm:ss.",
argument: new Argument<TimeSpan>(defaultValue: default(TimeSpan)) { Name = "duration-timespan" },
isHidden: true);
description: @"When specified, will trace for the given timespan and then automatically stop the trace. Provided in the form of dd:hh:mm:ss.")
{
Argument = new Argument<TimeSpan>(name: "duration-timespan", defaultValue: default),
IsHidden = true
};
}
}

Просмотреть файл

@ -2,14 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Tools.Common;
using System;
using System.IO;
using System.CommandLine;
using System.CommandLine.Builder;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
namespace Microsoft.Diagnostics.Tools.Trace
{
@ -45,25 +42,28 @@ namespace Microsoft.Diagnostics.Tools.Trace
public static Command ConvertCommand() =>
new Command(
name: "convert",
description: "Converts traces to alternate formats for use with alternate trace analysis tools. Can only convert from the nettrace format.",
argument: (new Argument<FileInfo>(defaultValue: new FileInfo(CollectCommandHandler.DefaultTraceName)) {
Name = "input-filename",
Description = $"Input trace file to be converted. Defaults to '{CollectCommandHandler.DefaultTraceName}'."
}).ExistingOnly(),
symbols: new Option[] {
CommonOptions.ConvertFormatOption(),
OutputOption()
},
handler: System.CommandLine.Invocation.CommandHandler.Create<IConsole, FileInfo, TraceFileFormat, FileInfo>(ConvertFile),
isHidden: false
);
description: "Converts traces to alternate formats for use with alternate trace analysis tools. Can only convert from the nettrace format")
{
// Handler
System.CommandLine.Invocation.CommandHandler.Create<IConsole, FileInfo, TraceFileFormat, FileInfo>(ConvertFile),
// Arguments and Options
InputFileArgument(),
CommonOptions.ConvertFormatOption(),
OutputOption(),
};
public static Option OutputOption() =>
private static Argument InputFileArgument() =>
new Argument<FileInfo>(name: "input-filename", defaultValue: new FileInfo(CollectCommandHandler.DefaultTraceName))
{
Description = $"Input trace file to be converted. Defaults to '{CollectCommandHandler.DefaultTraceName}'."
}.ExistingOnly();
private static Option OutputOption() =>
new Option(
aliases: new [] { "-o", "--output" },
description: "Output filename. Extension of target format will be added.",
argument: new Argument<FileInfo>() { Name = "output-filename" },
isHidden: false
);
aliases: new[] { "-o", "--output" },
description: "Output filename. Extension of target format will be added.")
{
Argument = new Argument<FileInfo>(name: "output-filename")
};
}
}

Просмотреть файл

@ -1,28 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Internal.Common.Commands;
using System;
using System.CommandLine;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.Tools.Trace
{
internal static class ListProcessesCommandHandler
{
public static async Task<int> GetActivePorts(IConsole console)
{
ProcessStatusCommandHandler.PrintProcessStatus(console);
await Task.FromResult(0);
return 0;
}
public static Command ListProcessesCommand() =>
new Command(
name: "ps",
description: "Lists dotnet processes that can be attached to.",
handler: System.CommandLine.Invocation.CommandHandler.Create<IConsole>(GetActivePorts),
isHidden: false);
}
}

Просмотреть файл

@ -35,9 +35,10 @@ namespace Microsoft.Diagnostics.Tools.Trace
public static Command ListProfilesCommand() =>
new Command(
name: "list-profiles",
description: "Lists pre-built tracing profiles with a description of what providers and filters are in each profile.",
handler: CommandHandler.Create<IConsole>(GetProfiles),
isHidden: false);
description: "Lists pre-built tracing profiles with a description of what providers and filters are in each profile")
{
Handler = CommandHandler.Create<IConsole>(GetProfiles),
};
internal static IEnumerable<Profile> DotNETRuntimeProfiles { get; } = new[] {
new Profile(

Просмотреть файл

@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
using System.CommandLine;
using System.IO;
using System.Runtime.InteropServices;
namespace Microsoft.Diagnostics.Tools.Trace
{
@ -13,24 +11,27 @@ namespace Microsoft.Diagnostics.Tools.Trace
public static Option ProcessIdOption() =>
new Option(
aliases: new[] { "-p", "--process-id" },
description: "The process to collect the trace from",
argument: new Argument<int> { Name = "pid" },
isHidden: false);
description: "The process id to collect the trace.")
{
Argument = new Argument<int>(name: "pid")
};
public static TraceFileFormat DefaultTraceFileFormat => TraceFileFormat.NetTrace;
public static Option FormatOption() =>
new Option(
aliases: new[] { "--format" },
description: $"Sets the output format for the trace file. Default is {DefaultTraceFileFormat}",
argument: new Argument<TraceFileFormat>(defaultValue: DefaultTraceFileFormat) { Name = "trace-file-format" },
isHidden: false);
alias: "--format",
description: $"Sets the output format for the trace file. Default is {DefaultTraceFileFormat}.")
{
Argument = new Argument<TraceFileFormat>(name: "trace-file-format", defaultValue: DefaultTraceFileFormat)
};
public static Option ConvertFormatOption() =>
new Option(
aliases: new[] { "--format" },
description: $"Sets the output format for the trace file conversion.",
argument: new Argument<TraceFileFormat> { Name = "trace-file-format" },
isHidden: false);
alias: "--format",
description: $"Sets the output format for the trace file conversion.")
{
Argument = new Argument<TraceFileFormat>(name: "trace-file-format")
};
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Internal.Common.Commands;
using System.CommandLine.Builder;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
@ -14,7 +15,7 @@ namespace Microsoft.Diagnostics.Tools.Trace
{
var parser = new CommandLineBuilder()
.AddCommand(CollectCommandHandler.CollectCommand())
.AddCommand(ListProcessesCommandHandler.ListProcessesCommand())
.AddCommand(ProcessStatusCommandHandler.ProcessStatusCommand("Lists the dotnet processes that traces can be collected"))
.AddCommand(ListProfilesCommandHandler.ListProfilesCommand())
.AddCommand(ConvertCommandHandler.ConvertCommand())
.UseDefaults()

Просмотреть файл

@ -20,6 +20,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\Common\CommandExtensions.cs" Link="CommandExtensions.cs" />
<Compile Include="..\Common\Commands\ProcessStatus.cs" Link="ProcessStatus.cs" />
</ItemGroup>