diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Activities.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Activities.cs
index 9f4665572436..0742764a8b7c 100644
--- a/src/Cli/Microsoft.DotNet.Cli.Utils/Activities.cs
+++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Activities.cs
@@ -6,15 +6,27 @@
namespace Microsoft.DotNet.Cli.Utils;
///
-/// Contains helpers for working with Activities in the .NET CLI.
+/// Contains helpers for working with Activities in the .NET CLI.
///
public static class Activities
{
-
///
/// The main entrypoint for creating Activities in the .NET CLI.
/// All activities created in the CLI should use this , to allow
/// consumers to easily filter and trace CLI activities.
///
public static ActivitySource Source { get; } = new("dotnet-cli", Product.Version);
+
+ ///
+ /// The environment variable used to transfer the chain of parent activity IDs.
+ /// This should be used when constructing new sub-processes in order to
+ /// track spans across calls.
+ ///
+ public const string TRACEPARENT = nameof(TRACEPARENT);
+ ///
+ /// The environment variable used to transfer the trace state of the parent activities.
+ /// This should be used when constructing new sub-processes in order to
+ /// track spans across calls.
+ ///
+ public const string TRACESTATE = nameof(TRACESTATE);
}
diff --git a/src/Cli/dotnet/CommonOptionsFactory.cs b/src/Cli/dotnet/CommonOptionsFactory.cs
index f776cd47712a..fa9dc531eb83 100644
--- a/src/Cli/dotnet/CommonOptionsFactory.cs
+++ b/src/Cli/dotnet/CommonOptionsFactory.cs
@@ -1,9 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-#nullable disable
-
using System.CommandLine;
+using System.CommandLine.Invocation;
+using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli;
@@ -19,6 +19,22 @@ internal static class CommonOptionsFactory
{
Description = CliStrings.SDKDiagnosticsCommandDefinition,
Recursive = recursive,
- Arity = ArgumentArity.Zero
+ Arity = ArgumentArity.Zero,
+ Action = new SetDiagnosticModeAction()
};
+
+ ///
+ /// Sets a few verbose diagnostics flags across the CLI.
+ /// Other commands may also use this to set their verbosity flags to a higher value or similar behaviors.
+ ///
+ internal class SetDiagnosticModeAction() : SynchronousCommandLineAction
+ {
+ public override int Invoke(ParseResult parseResult)
+ {
+ Environment.SetEnvironmentVariable(CommandLoggingContext.Variables.Verbose, bool.TrueString);
+ CommandLoggingContext.SetVerbose(true);
+ Reporter.Reset();
+ return 0;
+ }
+ }
}
diff --git a/src/Cli/dotnet/Extensions/ParseResultExtensions.cs b/src/Cli/dotnet/Extensions/ParseResultExtensions.cs
index 17d74908d42e..242bda2fe7d8 100644
--- a/src/Cli/dotnet/Extensions/ParseResultExtensions.cs
+++ b/src/Cli/dotnet/Extensions/ParseResultExtensions.cs
@@ -15,17 +15,17 @@ namespace Microsoft.DotNet.Cli.Extensions;
public static class ParseResultExtensions
{
- ///
+ ///
/// Finds the command of the parse result and invokes help for that command.
/// If no command is specified, invokes help for the application.
- ///
- ///
+ ///
+ ///
/// This is accomplished by finding a set of tokens that should be valid and appending a help token
/// to that list, then re-parsing the list of tokens. This is not ideal - either we should have a direct way
/// of invoking help for a ParseResult, or we should eliminate this custom, ad-hoc help invocation by moving
/// more situations that want to show help into Parsing Errors (which trigger help in the default System.CommandLine pipeline)
/// or custom Invocation Middleware, so we can more easily create our version of a HelpResult type.
- ///
+ ///
public static void ShowHelp(this ParseResult parseResult)
{
// take from the start of the list until we hit an option/--/unparsed token
@@ -56,14 +56,17 @@ public static void ShowHelpOrErrorIfAppropriate(this ParseResult parseResult)
}
}
- ///Splits a .NET format string by the format placeholders (the {N} parts) to get an array of the literal parts, to be used in message-checking
+ ///
+ /// Splits a .NET format string by the format placeholders (the {N} parts) to get an array of the literal parts, to be used in message-checking.
+ ///
static string[] DistinctFormatStringParts(string formatString)
{
return Regex.Split(formatString, @"{[0-9]+}"); // match the literal '{', followed by any of 0-9 one or more times, followed by the literal '}'
}
-
- /// given a string and a series of parts, ensures that all parts are present in the string in sequential order
+ ///
+ /// Given a string and a series of parts, ensures that all parts are present in the string in sequential order.
+ ///
static bool ErrorContainsAllParts(ReadOnlySpan error, string[] parts)
{
foreach (var part in parts)
@@ -85,9 +88,12 @@ static bool ErrorContainsAllParts(ReadOnlySpan error, string[] parts)
public static string RootSubCommandResult(this ParseResult parseResult)
{
- return parseResult.RootCommandResult.Children?
- .Select(child => GetSymbolResultValue(parseResult, child))
- .FirstOrDefault(subcommand => !string.IsNullOrEmpty(subcommand)) ?? string.Empty;
+ CommandResult commandResult = parseResult.CommandResult;
+ while (commandResult != parseResult.RootCommandResult && commandResult.Parent is CommandResult parentCommand)
+ {
+ commandResult = parentCommand;
+ }
+ return commandResult.Command.Name;
}
public static bool IsDotnetBuiltInCommand(this ParseResult parseResult)
@@ -101,12 +107,7 @@ public static bool IsTopLevelDotnetCommand(this ParseResult parseResult)
return parseResult.CommandResult.Command.Equals(Parser.RootCommand) && string.IsNullOrEmpty(parseResult.RootSubCommandResult());
}
- public static bool CanBeInvoked(this ParseResult parseResult)
- {
- return Parser.GetBuiltInCommand(parseResult.RootSubCommandResult()) != null ||
- parseResult.Tokens.Any(token => token.Type == TokenType.Directive) ||
- (parseResult.IsTopLevelDotnetCommand() && string.IsNullOrEmpty(parseResult.GetValue(Parser.DotnetSubCommand)));
- }
+ public static bool CanBeInvoked(this ParseResult parseResult) => parseResult.Action is not null;
public static int HandleMissingCommand(this ParseResult parseResult)
{
@@ -163,34 +164,14 @@ public static bool DiagOptionPrecedesSubcommand(this string[] args, string subCo
return false;
}
- private static string? GetSymbolResultValue(ParseResult parseResult, SymbolResult symbolResult) => symbolResult switch
- {
- CommandResult commandResult => commandResult.Command.Name,
- ArgumentResult argResult => argResult.Tokens.FirstOrDefault()?.Value,
- _ => parseResult.GetResult(Parser.DotnetSubCommand)?.GetValueOrDefault()
- };
-
public static bool BothArchAndOsOptionsSpecified(this ParseResult parseResult) =>
(parseResult.HasOption(CommonOptions.ArchitectureOption) ||
parseResult.HasOption(CommonOptions.LongFormArchitectureOption)) &&
parseResult.HasOption(CommonOptions.OperatingSystemOption);
- internal static string? GetCommandLineRuntimeIdentifier(this ParseResult parseResult)
- {
- return parseResult.HasOption(CommonOptions.RuntimeOptionName) ?
- parseResult.GetValue(CommonOptions.RuntimeOptionName) :
- parseResult.HasOption(CommonOptions.OperatingSystemOption) ||
- parseResult.HasOption(CommonOptions.ArchitectureOption) ||
- parseResult.HasOption(CommonOptions.LongFormArchitectureOption) ?
- CommonOptions.ResolveRidShorthandOptionsToRuntimeIdentifier(
- parseResult.GetValue(CommonOptions.OperatingSystemOption),
- CommonOptions.ArchOptionValue(parseResult)) :
- null;
- }
-
public static bool UsingRunCommandShorthandProjectOption(this ParseResult parseResult)
{
- if (parseResult.HasOption(RunCommandParser.PropertyOption) && parseResult.GetValue(RunCommandParser.PropertyOption)!.Any())
+ if (parseResult.HasOption(RunCommandParser.PropertyOption) && (parseResult.GetValue(RunCommandParser.PropertyOption)?.Any() ?? false))
{
var projVals = parseResult.GetRunCommandShorthandProjectValues();
if (projVals?.Any() is true)
diff --git a/src/Cli/dotnet/Parser.cs b/src/Cli/dotnet/Parser.cs
index 4bddb07f976e..bb9c9e236f48 100644
--- a/src/Cli/dotnet/Parser.cs
+++ b/src/Cli/dotnet/Parser.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-#nullable disable
-
using System.CommandLine;
using System.CommandLine.Completions;
using System.CommandLine.Invocation;
@@ -96,14 +94,44 @@ public static class Parser
public static readonly Option VersionOption = new("--version")
{
- Arity = ArgumentArity.Zero
+ Arity = ArgumentArity.Zero,
+ Action = new PrintVersionAction()
};
+ internal class PrintVersionAction : SynchronousCommandLineAction
+ {
+ public PrintVersionAction()
+ {
+ Terminating = true;
+ }
+
+ public override int Invoke(ParseResult parseResult)
+ {
+ CommandLineInfo.PrintVersion();
+ return 0;
+ }
+ }
+
public static readonly Option InfoOption = new("--info")
{
- Arity = ArgumentArity.Zero
+ Arity = ArgumentArity.Zero,
+ Action = new PrintInfoAction()
};
+ internal class PrintInfoAction : SynchronousCommandLineAction
+ {
+ public PrintInfoAction()
+ {
+ Terminating = true;
+ }
+
+ public override int Invoke(ParseResult parseResult)
+ {
+ CommandLineInfo.PrintInfo();
+ return 0;
+ }
+ }
+
public static readonly Option ListSdksOption = new("--list-sdks")
{
Arity = ArgumentArity.Zero
@@ -169,31 +197,22 @@ private static RootCommand ConfigureCommandLine(RootCommand rootCommand)
rootCommand.SetAction(parseResult =>
{
- if (parseResult.GetValue(DiagOption) && parseResult.Tokens.Count == 1)
- {
- // when user does not specify any args except of diagnostics ("dotnet -d"), we do nothing
- // as Program.ProcessArgs already enabled the diagnostic output
- return 0;
- }
- else
- {
- // when user does not specify any args (just "dotnet"), a usage needs to be printed
- parseResult.InvocationConfiguration.Output.WriteLine(CliUsage.HelpText);
- return 0;
- }
+ // when user does not specify any args (just "dotnet"), a usage needs to be printed
+ parseResult.InvocationConfiguration.Output.WriteLine(CliUsage.HelpText);
+ return 0;
});
return rootCommand;
}
- public static Command GetBuiltInCommand(string commandName) =>
+ public static Command? GetBuiltInCommand(string commandName) =>
Subcommands.FirstOrDefault(c => c.Name.Equals(commandName, StringComparison.OrdinalIgnoreCase));
///
/// Implements token-per-line response file handling for the CLI. We use this instead of the built-in S.CL handling
/// to ensure backwards-compatibility with MSBuild.
///
- public static bool TokenPerLine(string tokenToReplace, out IReadOnlyList replacementTokens, out string errorMessage)
+ public static bool TokenPerLine(string tokenToReplace, out IReadOnlyList? replacementTokens, out string? errorMessage)
{
var filePath = Path.GetFullPath(tokenToReplace);
if (File.Exists(filePath))
@@ -256,8 +275,7 @@ public static bool TokenPerLine(string tokenToReplace, out IReadOnlyList
public static int Invoke(string[] args) => Invoke(Parse(args));
public static Task InvokeAsync(string[] args, CancellationToken cancellationToken = default) => InvokeAsync(Parse(args), cancellationToken);
-
- internal static int ExceptionHandler(Exception exception, ParseResult parseResult)
+ internal static int ExceptionHandler(Exception? exception, ParseResult parseResult)
{
if (exception is TargetInvocationException)
{
@@ -277,13 +295,13 @@ internal static int ExceptionHandler(Exception exception, ParseResult parseResul
exception.Message.Red().Bold());
parseResult.ShowHelp();
}
- else if (exception.GetType().Name.Equals("WorkloadManifestCompositionException"))
+ else if (exception is not null && exception.GetType().Name.Equals("WorkloadManifestCompositionException"))
{
Reporter.Error.WriteLine(CommandLoggingContext.IsVerbose ?
exception.ToString().Red().Bold() :
exception.Message.Red().Bold());
}
- else
+ else if (exception is not null)
{
Reporter.Error.Write("Unhandled exception: ".Red().Bold());
Reporter.Error.WriteLine(CommandLoggingContext.IsVerbose ?
@@ -371,7 +389,7 @@ public override void Write(HelpContext context)
}
else if (command.Name.Equals(FormatCommandParser.GetCommand().Name))
{
- var arguments = context.ParseResult.GetValue(FormatCommandParser.Arguments);
+ var arguments = context.ParseResult.GetValue(FormatCommandParser.Arguments) ?? [];
new FormatForwardingApp([.. arguments, .. helpArgs]).Execute();
}
else if (command.Name.Equals(FsiCommandParser.GetCommand().Name))
@@ -398,9 +416,9 @@ public override void Write(HelpContext context)
{
if (command.Name.Equals(ListReferenceCommandParser.GetCommand().Name))
{
- Command listCommand = command.Parents.Single() as Command;
+ Command? listCommand = command.Parents.Single() as Command;
- for (int i = 0; i < listCommand.Arguments.Count; i++)
+ for (int i = 0; i < listCommand?.Arguments.Count; i++)
{
if (listCommand.Arguments[i].Name == CliStrings.SolutionOrProjectArgumentName)
{
@@ -431,6 +449,7 @@ internal PrintCliSchemaAction()
{
Terminating = true;
}
+
public override int Invoke(ParseResult parseResult)
{
CliSchema.PrintCliSchema(parseResult.CommandResult, parseResult.InvocationConfiguration.Output, Program.TelemetryClient);
diff --git a/src/Cli/dotnet/PerformanceLogEventSource.cs b/src/Cli/dotnet/PerformanceLogEventSource.cs
index 0879b02f1725..2957abedd93b 100644
--- a/src/Cli/dotnet/PerformanceLogEventSource.cs
+++ b/src/Cli/dotnet/PerformanceLogEventSource.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-#nullable disable
-
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Reflection;
@@ -21,7 +19,7 @@ private PerformanceLogEventSource()
}
[NonEvent]
- internal void LogStartUpInformation(PerformanceLogStartupInformation startupInfo)
+ internal void LogStartUpInformation(PerformanceLogStartupInformation? startupInfo)
{
if (!IsEnabled())
{
@@ -33,7 +31,7 @@ internal void LogStartUpInformation(PerformanceLogStartupInformation startupInfo
LogMachineConfiguration();
OSInfo(RuntimeEnvironment.OperatingSystem, RuntimeEnvironment.OperatingSystemVersion, RuntimeEnvironment.OperatingSystemPlatform.ToString());
- SDKInfo(Product.Version, commitSha, RuntimeInformation.RuntimeIdentifier, versionFile.BuildRid, AppContext.BaseDirectory);
+ SDKInfo(Product.Version, commitSha, RuntimeInformation.RuntimeIdentifier, versionFile.BuildRid!, AppContext.BaseDirectory);
EnvironmentInfo(Environment.CommandLine);
LogMemoryConfiguration();
LogDrives();
@@ -44,7 +42,7 @@ internal void LogStartUpInformation(PerformanceLogStartupInformation startupInfo
{
if (startupInfo.TimedAssembly != null)
{
- AssemblyLoad(startupInfo.TimedAssembly.GetName().Name, startupInfo.AssemblyLoadTime.TotalMilliseconds);
+ AssemblyLoad(startupInfo.TimedAssembly.GetName().Name!, startupInfo.AssemblyLoadTime.TotalMilliseconds);
}
Process currentProcess = Process.GetCurrentProcess();
@@ -312,7 +310,7 @@ public PerformanceLogStartupInformation(DateTime mainTimeStamp)
}
internal DateTime MainTimeStamp { get; private set; }
- internal Assembly TimedAssembly { get; private set; }
+ internal Assembly? TimedAssembly { get; private set; }
internal TimeSpan AssemblyLoadTime { get; private set; }
private void MeasureModuleLoad()
@@ -323,7 +321,7 @@ private void MeasureModuleLoad()
{
foreach (Assembly loadedAssembly in AppDomain.CurrentDomain.GetAssemblies())
{
- if (loadedAssembly.GetName().Name.Equals(assemblyName))
+ if (loadedAssembly.GetName().Name is string n && n.Equals(assemblyName))
{
// If the assembly is already loaded, then bail.
return;
@@ -426,7 +424,7 @@ private void Initialize()
{
using (StreamReader reader = new(File.OpenRead("/proc/meminfo")))
{
- string line;
+ string? line;
while (!Valid && ((line = reader.ReadLine()) != null))
{
if (line.StartsWith(MemTotal) || line.StartsWith(MemAvailable))
diff --git a/src/Cli/dotnet/Program.cs b/src/Cli/dotnet/Program.cs
index d88858b67e33..34a6af49ceb6 100644
--- a/src/Cli/dotnet/Program.cs
+++ b/src/Cli/dotnet/Program.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-#nullable disable
-
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Diagnostics;
@@ -24,307 +22,348 @@ namespace Microsoft.DotNet.Cli;
public class Program
{
- private static readonly string ToolPathSentinelFileName = $"{Product.Version}.toolpath.sentinel";
-
- public static ITelemetry TelemetryClient;
- public static int Main(string[] args)
+ private static readonly string s_toolPathSentinelFileName = $"{Product.Version}.toolpath.sentinel";
+ public static ITelemetry TelemetryClient { get; }
+ internal static PerformanceLogEventListener? performanceLogEventListener;
+ private static Dictionary performanceData = [];
+ private static readonly Activity? s_mainActivity;
+ private static readonly DateTime s_mainTimeStamp;
+
+ static Program()
{
- using AutomaticEncodingRestorer _ = new();
-
- // Setting output encoding is not available on those platforms
- if (UILanguageOverride.OperatingSystemSupportsUtf8())
- {
- Console.OutputEncoding = Encoding.UTF8;
- }
-
- DebugHelper.HandleDebugSwitch(ref args);
-
- // Capture the current timestamp to calculate the host overhead.
- DateTime mainTimeStamp = DateTime.Now;
- TimeSpan startupTime = mainTimeStamp - Process.GetCurrentProcess().StartTime;
-
+ s_mainTimeStamp = DateTime.Now;
+ s_mainActivity = Activities.Source.CreateActivity("main", kind: ActivityKind.Internal);
+ s_mainActivity?.SetStartTime(Process.GetCurrentProcess().StartTime);
+ TrackHostStartup(s_mainTimeStamp);
+ SetupMSBuildEnvironmentInvariants();
bool perfLogEnabled = Env.GetEnvironmentVariableAsBool("DOTNET_CLI_PERF_LOG", false);
-
- if (string.IsNullOrEmpty(Env.GetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD")))
- {
- Environment.SetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD", "1");
- }
-
// Avoid create temp directory with root permission and later prevent access in non sudo
if (SudoEnvironmentDirectoryOverride.IsRunningUnderSudo())
{
perfLogEnabled = false;
}
-
- PerformanceLogStartupInformation startupInfo = null;
if (perfLogEnabled)
{
- startupInfo = new PerformanceLogStartupInformation(mainTimeStamp);
PerformanceLogManager.InitializeAndStartCleanup(FileSystemWrapper.Default);
+ performanceLogEventListener = PerformanceLogEventListener.Create(FileSystemWrapper.Default, PerformanceLogManager.Instance.CurrentLogDirectory);
+ }
+ else
+ {
+ performanceLogEventListener = null;
}
+ TelemetryClient = InitializeTelemetry();
- PerformanceLogEventListener perLogEventListener = null;
- try
+ }
+
+ public static int Main(string[] args)
+ {
+ using AutomaticEncodingRestorer _encodingRestorer = new();
+ TimeSpan startupTime = s_mainTimeStamp - Process.GetCurrentProcess().StartTime;
+ performanceData.Add("Startup Time", startupTime.TotalMilliseconds);
+ // Setting output encoding is not available on those platforms
+ if (UILanguageOverride.OperatingSystemSupportsUtf8())
{
- if (perfLogEnabled)
- {
- perLogEventListener = PerformanceLogEventListener.Create(FileSystemWrapper.Default, PerformanceLogManager.Instance.CurrentLogDirectory);
- }
+ Console.OutputEncoding = Encoding.UTF8;
+ }
+ DebugHelper.HandleDebugSwitch(ref args);
+ PerformanceLogStartupInformation? startupInfo = null;
+ if (performanceLogEventListener != null)
+ {
+ startupInfo = new PerformanceLogStartupInformation(s_mainTimeStamp);
+ }
+ try
+ {
PerformanceLogEventSource.Log.LogStartUpInformation(startupInfo);
PerformanceLogEventSource.Log.CLIStart();
-
InitializeProcess();
+ return ProcessArgs(args);
+ }
+ catch (Exception e) when (e.ShouldBeDisplayedAsError())
+ {
+ Reporter.Error.WriteLine(CommandLoggingContext.IsVerbose
+ ? e.ToString().Red().Bold()
+ : e.Message.Red().Bold());
- try
+ if (e is CommandParsingException commandParsingException && commandParsingException.ParseResult != null)
{
- return ProcessArgs(args, startupTime);
+ commandParsingException.ParseResult.ShowHelp();
}
- catch (Exception e) when (e.ShouldBeDisplayedAsError())
- {
- Reporter.Error.WriteLine(CommandLoggingContext.IsVerbose
- ? e.ToString().Red().Bold()
- : e.Message.Red().Bold());
-
- var commandParsingException = e as CommandParsingException;
- if (commandParsingException != null && commandParsingException.ParseResult != null)
- {
- commandParsingException.ParseResult.ShowHelp();
- }
- return 1;
- }
- catch (Exception e) when (!e.ShouldBeDisplayedAsError())
- {
- // If telemetry object has not been initialized yet. It cannot be collected
- TelemetryEventEntry.SendFiltered(e);
- Reporter.Error.WriteLine(e.ToString().Red().Bold());
+ return 1;
+ }
+ catch (Exception e) when (!e.ShouldBeDisplayedAsError())
+ {
+ // If telemetry object has not been initialized yet. It cannot be collected
+ TelemetryEventEntry.SendFiltered(e);
+ Reporter.Error.WriteLine(e.ToString().Red().Bold());
- return 1;
- }
- finally
- {
- PerformanceLogEventSource.Log.CLIStop();
- }
+ return 1;
}
finally
{
- if (perLogEventListener != null)
- {
- perLogEventListener.Dispose();
- }
+ PerformanceLogEventSource.Log.TelemetryClientFlushStart();
+ TelemetryClient.Flush();
+ PerformanceLogEventSource.Log.TelemetryClientFlushStop();
+ PerformanceLogEventSource.Log.CLIStop();
+ Shutdown();
}
}
- internal static int ProcessArgs(string[] args)
+ public static void Shutdown()
{
- return ProcessArgs(args, new TimeSpan(0));
+ s_mainActivity?.Stop();
+ performanceLogEventListener?.Dispose();
+ Activities.Source.Dispose();
}
- internal static int ProcessArgs(string[] args, TimeSpan startupTime)
+ private static void TrackHostStartup(DateTime mainTimeStamp)
{
- Dictionary performanceData = [];
+ using var hostStartupActivity = Activities.Source.StartActivity("host-startup");
+ hostStartupActivity?.SetStartTime(Process.GetCurrentProcess().StartTime);
+ hostStartupActivity?.SetEndTime(mainTimeStamp);
+ hostStartupActivity?.SetStatus(ActivityStatusCode.Ok);
+ }
- PerformanceLogEventSource.Log.BuiltInCommandParserStart();
- ParseResult parseResult;
- using (new PerformanceMeasurement(performanceData, "Parse Time"))
+ ///
+ /// We have some behaviors in MSBuild that we want to enforce (either when using MSBuild API or by shelling out to it),
+ /// so we set those ASAP as globally as possible.
+ ///
+ private static void SetupMSBuildEnvironmentInvariants()
+ {
+ if (string.IsNullOrEmpty(Env.GetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD")))
{
- parseResult = Parser.Parse(args);
+ Environment.SetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD", "1");
+ }
+ }
- // Avoid create temp directory with root permission and later prevent access in non sudo
- // This method need to be run very early before temp folder get created
- // https://github.com/dotnet/sdk/issues/20195
- SudoEnvironmentDirectoryOverride.OverrideEnvironmentVariableToTmp(parseResult);
+ private static string GetCommandName(ParseResult r)
+ {
+ if (r.Action is Parser.PrintVersionAction)
+ {
+ // If the action is PrintVersionAction, we return the command name as "dotnet --version"
+ return "dotnet --version";
+ }
+ else if (r.Action is Parser.PrintInfoAction)
+ {
+ // If the action is PrintHelpAction, we return the command name as "dotnet --help"
+ return "dotnet --info";
}
- PerformanceLogEventSource.Log.BuiltInCommandParserStop();
- using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel())
+ // Walk the parent command tree to find the top-level command name and get the full command name for this parseresult.
+ List parentNames = [r.CommandResult.Command.Name];
+ var current = r.CommandResult.Parent;
+ while (current is CommandResult parentCommandResult)
{
- IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel;
- IAspNetCertificateSentinel aspNetCertificateSentinel = new AspNetCertificateSentinel();
- IFileSentinel toolPathSentinel = new FileSentinel(new FilePath(Path.Combine(CliFolderPathCalculator.DotnetUserProfileFolderPath, ToolPathSentinelFileName)));
+ parentNames.Add(parentCommandResult.Command.Name);
+ current = parentCommandResult.Parent;
+ }
+ parentNames.Reverse();
+ return string.Join(' ', parentNames);
+ }
- PerformanceLogEventSource.Log.TelemetryRegistrationStart();
+ private static void SetDisplayName(Activity? activity, ParseResult parseResult)
+ {
+ if (activity == null)
+ {
+ return;
+ }
+ var name = GetCommandName(parseResult);
- TelemetryClient ??= new Telemetry.Telemetry(firstTimeUseNoticeSentinel);
- TelemetryEventEntry.Subscribe(TelemetryClient.TrackEvent);
- TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing);
+ // Set the display name to the full command name
+ activity.DisplayName = name;
- PerformanceLogEventSource.Log.TelemetryRegistrationStop();
+ // Set the command name as an attribute for better filtering in telemetry
+ activity.SetTag("command.name", name);
+ }
- if (parseResult.GetValue(Parser.DiagOption) && parseResult.IsDotnetBuiltInCommand())
- {
- // We found --diagnostic or -d, but we still need to determine whether the option should
- // be attached to the dotnet command or the subcommand.
- if (args.DiagOptionPrecedesSubcommand(parseResult.RootSubCommandResult()))
- {
- Environment.SetEnvironmentVariable(CommandLoggingContext.Variables.Verbose, bool.TrueString);
- CommandLoggingContext.SetVerbose(true);
- Reporter.Reset();
- }
- }
- if (parseResult.HasOption(Parser.VersionOption) && parseResult.IsTopLevelDotnetCommand())
- {
- CommandLineInfo.PrintVersion();
- return 0;
- }
- else if (parseResult.HasOption(Parser.InfoOption) && parseResult.IsTopLevelDotnetCommand())
+ internal static int ProcessArgs(string[] args)
+ {
+ ParseResult parseResult = ParseArgs(args);
+ SetupDotnetFirstRun(parseResult);
+
+ if (parseResult.CanBeInvoked())
+ {
+ PerformanceLogEventSource.Log.TelemetrySaveIfEnabledStart();
+ TelemetryEventEntry.SendFiltered(Tuple.Create(parseResult, performanceData));
+ PerformanceLogEventSource.Log.TelemetrySaveIfEnabledStop();
+ InvokeBuiltInCommand(parseResult, out var exitCode);
+ return exitCode;
+ }
+ else
+ {
+ try
{
- CommandLineInfo.PrintInfo();
- return 0;
+ return LookupAndExecuteCommand(args, parseResult);
}
- else
+ catch (CommandUnknownException e)
{
- PerformanceLogEventSource.Log.FirstTimeConfigurationStart();
-
- var environmentProvider = new EnvironmentProvider();
-
- bool generateAspNetCertificate = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_GENERATE_ASPNET_CERTIFICATE, defaultValue: true);
- bool telemetryOptout = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.TELEMETRY_OPTOUT, defaultValue: CompileOptions.TelemetryOptOutDefault);
- bool addGlobalToolsToPath = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_ADD_GLOBAL_TOOLS_TO_PATH, defaultValue: true);
- bool nologo = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_NOLOGO, defaultValue: false);
- bool skipWorkloadIntegrityCheck = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_SKIP_WORKLOAD_INTEGRITY_CHECK,
- // Default the workload integrity check skip to true if the command is being ran in CI. Otherwise, false.
- defaultValue: new CIEnvironmentDetectorForTelemetry().IsCIEnvironment());
-
- ReportDotnetHomeUsage(environmentProvider);
-
- var isDotnetBeingInvokedFromNativeInstaller = false;
- if (parseResult.CommandResult.Command.Name.Equals(Parser.InstallSuccessCommand.Name))
- {
- aspNetCertificateSentinel = new NoOpAspNetCertificateSentinel();
- firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel();
- toolPathSentinel = new NoOpFileSentinel(exists: false);
- isDotnetBeingInvokedFromNativeInstaller = true;
- }
-
- var dotnetFirstRunConfiguration = new DotnetFirstRunConfiguration(
- generateAspNetCertificate: generateAspNetCertificate,
- telemetryOptout: telemetryOptout,
- addGlobalToolsToPath: addGlobalToolsToPath,
- nologo: nologo,
- skipWorkloadIntegrityCheck: skipWorkloadIntegrityCheck);
-
- string[] getStarOperators = ["getProperty", "getItem", "getTargetResult"];
- char[] switchIndicators = ['-', '/'];
- var getStarOptionPassed = parseResult.CommandResult.Tokens.Any(t =>
- getStarOperators.Any(o =>
- switchIndicators.Any(i => t.Value.StartsWith(i + o, StringComparison.OrdinalIgnoreCase))));
-
- ConfigureDotNetForFirstTimeUse(
- firstTimeUseNoticeSentinel,
- aspNetCertificateSentinel,
- toolPathSentinel,
- isDotnetBeingInvokedFromNativeInstaller,
- dotnetFirstRunConfiguration,
- environmentProvider,
- performanceData,
- skipFirstTimeUseCheck: getStarOptionPassed);
- PerformanceLogEventSource.Log.FirstTimeConfigurationStop();
+ Reporter.Error.WriteLine(e.Message.Red());
+ Reporter.Output.WriteLine(e.InstructionMessage);
+ return 1;
}
}
+ }
- if (CommandLoggingContext.IsVerbose)
+ private static int LookupAndExecuteCommand(string[] args, ParseResult parseResult)
+ {
+ var lookupExternalCommandActivity = Activities.Source.StartActivity("lookup-external-command");
+ PerformanceLogEventSource.Log.ExtensibleCommandResolverStart();
+ string commandName = "dotnet-" + parseResult.GetValue(Parser.DotnetSubCommand);
+ var resolvedCommandSpec = CommandResolver.TryResolveCommandSpec(
+ new DefaultCommandResolverPolicy(),
+ commandName,
+ args.GetSubArguments(),
+ FrameworkConstants.CommonFrameworks.NetStandardApp15);
+ lookupExternalCommandActivity?.Dispose();
+
+ if (resolvedCommandSpec is null && TryRunFileBasedApp(parseResult) is { } fileBasedAppExitCode)
{
- Console.WriteLine($"Telemetry is: {(TelemetryClient.Enabled ? "Enabled" : "Disabled")}");
+ lookupExternalCommandActivity?.Dispose();
+ return fileBasedAppExitCode;
}
+ else
+ {
+ var resolvedCommand = CommandFactoryUsingResolver.CreateOrThrow(commandName, resolvedCommandSpec);
+ PerformanceLogEventSource.Log.ExtensibleCommandResolverStop();
+ lookupExternalCommandActivity?.Dispose();
+
+ PerformanceLogEventSource.Log.ExtensibleCommandStart();
+ using var _executionActivity = Activities.Source.StartActivity("execute-extensible-command");
+ var result = resolvedCommand.Execute();
+ PerformanceLogEventSource.Log.ExtensibleCommandStop();
+ return result.ExitCode;
+ }
+ }
+
+ private static void InvokeBuiltInCommand(ParseResult parseResult, out int exitCode)
+ {
+ Debug.Assert(parseResult.CanBeInvoked());
+ using var _invocationActivity = Activities.Source.StartActivity("invocation");
PerformanceLogEventSource.Log.TelemetrySaveIfEnabledStart();
- performanceData.Add("Startup Time", startupTime.TotalMilliseconds);
TelemetryEventEntry.SendFiltered(Tuple.Create(parseResult, performanceData));
PerformanceLogEventSource.Log.TelemetrySaveIfEnabledStop();
-
- int exitCode;
- if (parseResult.CanBeInvoked())
+ try
{
- InvokeBuiltInCommand(parseResult, out exitCode);
+ exitCode = parseResult.Invoke();
+ exitCode = AdjustExitCode(parseResult, exitCode);
}
- else
+ catch (Exception exception)
{
- PerformanceLogEventSource.Log.ExtensibleCommandResolverStart();
- try
+ exitCode = Parser.ExceptionHandler(exception, parseResult);
+ }
+ }
+
+ private static int? TryRunFileBasedApp(ParseResult parseResult)
+ {
+ // If we didn't match any built-in commands, and a C# file path is the first argument,
+ // parse as `dotnet run file.cs ..rest_of_args` instead.
+ if (parseResult.CommandResult.Command is RootCommand
+ && parseResult.GetValue(Parser.DotnetSubCommand) is { } unmatchedCommandOrFile
+ && VirtualProjectBuildingCommand.IsValidEntryPointPath(unmatchedCommandOrFile))
+ {
+ List otherTokens = new(parseResult.Tokens.Count - 1);
+ foreach (var token in parseResult.Tokens)
{
- string commandName = "dotnet-" + parseResult.GetValue(Parser.DotnetSubCommand);
- var resolvedCommandSpec = CommandResolver.TryResolveCommandSpec(
- new DefaultCommandResolverPolicy(),
- commandName,
- args.GetSubArguments(),
- FrameworkConstants.CommonFrameworks.NetStandardApp15);
-
- if (resolvedCommandSpec is null && TryRunFileBasedApp(parseResult) is { } fileBasedAppExitCode)
+ if (token.Type != TokenType.Argument || token.Value != unmatchedCommandOrFile)
{
- exitCode = fileBasedAppExitCode;
- }
- else
- {
- var resolvedCommand = CommandFactoryUsingResolver.CreateOrThrow(commandName, resolvedCommandSpec);
- PerformanceLogEventSource.Log.ExtensibleCommandResolverStop();
-
- PerformanceLogEventSource.Log.ExtensibleCommandStart();
- var result = resolvedCommand.Execute();
- PerformanceLogEventSource.Log.ExtensibleCommandStop();
-
- exitCode = result.ExitCode;
+ otherTokens.Add(token.Value);
}
}
- catch (CommandUnknownException e)
- {
- Reporter.Error.WriteLine(e.Message.Red());
- Reporter.Output.WriteLine(e.InstructionMessage);
- exitCode = 1;
- }
- }
+ parseResult = Parser.Parse(["run", "--file", unmatchedCommandOrFile, .. otherTokens]);
- PerformanceLogEventSource.Log.TelemetryClientFlushStart();
- TelemetryClient.Flush();
- PerformanceLogEventSource.Log.TelemetryClientFlushStop();
+ InvokeBuiltInCommand(parseResult, out var exitCode);
+ return exitCode;
+ }
- TelemetryClient.Dispose();
+ return null;
+ }
- return exitCode;
+ private static ITelemetry InitializeTelemetry()
+ {
+ PerformanceLogEventSource.Log.TelemetryRegistrationStart();
+ var telemetryClient = new Telemetry.Telemetry();
+ TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent);
+ TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing);
+ PerformanceLogEventSource.Log.TelemetryRegistrationStop();
- static int? TryRunFileBasedApp(ParseResult parseResult)
+ if (CommandLoggingContext.IsVerbose)
{
- // If we didn't match any built-in commands, and a C# file path is the first argument,
- // parse as `dotnet run --file file.cs ..rest_of_args` instead.
- if (parseResult.GetValue(Parser.DotnetSubCommand) is { } unmatchedCommandOrFile
- && VirtualProjectBuildingCommand.IsValidEntryPointPath(unmatchedCommandOrFile))
- {
- List otherTokens = new(parseResult.Tokens.Count - 1);
- foreach (var token in parseResult.Tokens)
- {
- if (token.Type != TokenType.Argument || token.Value != unmatchedCommandOrFile)
- {
- otherTokens.Add(token.Value);
- }
- }
+ Console.WriteLine($"Telemetry is: {(telemetryClient.Enabled ? "Enabled" : "Disabled")}");
+ }
- parseResult = Parser.Parse(["run", "--file", unmatchedCommandOrFile, .. otherTokens]);
+ return telemetryClient;
+ }
- InvokeBuiltInCommand(parseResult, out var exitCode);
- return exitCode;
- }
+ private static ParseResult ParseArgs(string[] args)
+ {
+ ParseResult parseResult;
+ using (new PerformanceMeasurement(performanceData, "Parse Time"))
+ using (var _parseActivity = Activities.Source.StartActivity("parse"))
+ {
+ parseResult = Parser.Parse(args);
- return null;
+ // Avoid create temp directory with root permission and later prevent access in non sudo
+ // This method need to be run very early before temp folder get created
+ // https://github.com/dotnet/sdk/issues/20195
+ SudoEnvironmentDirectoryOverride.OverrideEnvironmentVariableToTmp(parseResult);
}
+ PerformanceLogEventSource.Log.BuiltInCommandParserStop();
+ SetDisplayName(s_mainActivity, parseResult);
+ return parseResult;
+ }
- static void InvokeBuiltInCommand(ParseResult parseResult, out int exitCode)
+ private static void SetupDotnetFirstRun(ParseResult parseResult)
+ {
+ PerformanceLogEventSource.Log.FirstTimeConfigurationStart();
+ using var _ = Activities.Source.StartActivity("first-time-use");
+ IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel();
+ IAspNetCertificateSentinel aspNetCertificateSentinel = new AspNetCertificateSentinel();
+ string toolPath = Path.Combine(CliFolderPathCalculator.DotnetUserProfileFolderPath, s_toolPathSentinelFileName);
+ IFileSentinel toolPathSentinel = new FileSentinel(new FilePath(toolPath));
+
+ var environmentProvider = new EnvironmentProvider();
+ bool generateAspNetCertificate = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_GENERATE_ASPNET_CERTIFICATE, defaultValue: true);
+ bool telemetryOptout = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.TELEMETRY_OPTOUT, defaultValue: CompileOptions.TelemetryOptOutDefault);
+ bool addGlobalToolsToPath = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_ADD_GLOBAL_TOOLS_TO_PATH, defaultValue: true);
+ bool nologo = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_NOLOGO, defaultValue: false);
+ bool skipWorkloadIntegrityCheck = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_SKIP_WORKLOAD_INTEGRITY_CHECK,
+ // Default the workload integrity check skip to true if the command is being ran in CI. Otherwise, false.
+ defaultValue: new CIEnvironmentDetectorForTelemetry().IsCIEnvironment());
+
+ ReportDotnetHomeUsage(environmentProvider);
+
+ var isDotnetBeingInvokedFromNativeInstaller = false;
+ if (parseResult.CommandResult.Command.Name.Equals(Parser.InstallSuccessCommand.Name))
{
- Debug.Assert(parseResult.CanBeInvoked());
+ aspNetCertificateSentinel = new NoOpAspNetCertificateSentinel();
+ firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel();
+ toolPathSentinel = new NoOpFileSentinel(exists: false);
+ isDotnetBeingInvokedFromNativeInstaller = true;
+ }
- PerformanceLogEventSource.Log.BuiltInCommandStart();
+ var dotnetFirstRunConfiguration = new DotnetFirstRunConfiguration(
+ generateAspNetCertificate: generateAspNetCertificate,
+ telemetryOptout: telemetryOptout,
+ addGlobalToolsToPath: addGlobalToolsToPath,
+ nologo: nologo,
+ skipWorkloadIntegrityCheck: skipWorkloadIntegrityCheck);
- try
- {
- exitCode = Parser.Invoke(parseResult);
- exitCode = AdjustExitCode(parseResult, exitCode);
- }
- catch (Exception exception)
- {
- exitCode = Parser.ExceptionHandler(exception, parseResult);
- }
+ string[] getStarOperators = ["getProperty", "getItem", "getTargetResult"];
+ char[] switchIndicators = ['-', '/'];
+ var getStarOptionPassed = parseResult.CommandResult.Tokens.Any(t =>
+ getStarOperators.Any(o =>
+ switchIndicators.Any(i => t.Value.StartsWith(i + o, StringComparison.OrdinalIgnoreCase))));
- PerformanceLogEventSource.Log.BuiltInCommandStop();
- }
+ ConfigureDotNetForFirstTimeUse(
+ firstTimeUseNoticeSentinel,
+ aspNetCertificateSentinel,
+ toolPathSentinel,
+ isDotnetBeingInvokedFromNativeInstaller,
+ dotnetFirstRunConfiguration,
+ environmentProvider,
+ skipFirstTimeUseCheck: getStarOptionPassed);
+ PerformanceLogEventSource.Log.FirstTimeConfigurationStop();
}
private static int AdjustExitCode(ParseResult parseResult, int exitCode)
@@ -357,11 +396,7 @@ private static void ReportDotnetHomeUsage(IEnvironmentProvider provider)
return;
}
- Reporter.Verbose.WriteLine(
- string.Format(
- LocalizableStrings.DotnetCliHomeUsed,
- home,
- CliFolderPathCalculator.DotnetHomeVariableName));
+ Reporter.Verbose.WriteLine(string.Format(LocalizableStrings.DotnetCliHomeUsed, home, CliFolderPathCalculator.DotnetHomeVariableName));
}
private static void ConfigureDotNetForFirstTimeUse(
@@ -371,7 +406,6 @@ private static void ConfigureDotNetForFirstTimeUse(
bool isDotnetBeingInvokedFromNativeInstaller,
DotnetFirstRunConfiguration dotnetFirstRunConfiguration,
IEnvironmentProvider environmentProvider,
- Dictionary performanceMeasurements,
bool skipFirstTimeUseCheck)
{
var isFirstTimeUse = !firstTimeUseNoticeSentinel.Exists() && !skipFirstTimeUseCheck;
@@ -387,7 +421,6 @@ private static void ConfigureDotNetForFirstTimeUse(
dotnetFirstRunConfiguration,
reporter,
environmentPath,
- performanceMeasurements,
skipFirstTimeUseCheck: skipFirstTimeUseCheck);
dotnetConfigurer.Configure();
diff --git a/src/Cli/dotnet/Telemetry/Telemetry.cs b/src/Cli/dotnet/Telemetry/Telemetry.cs
index ba190b169500..5807ec380c3d 100644
--- a/src/Cli/dotnet/Telemetry/Telemetry.cs
+++ b/src/Cli/dotnet/Telemetry/Telemetry.cs
@@ -17,7 +17,7 @@ public class Telemetry : ITelemetry
internal static bool DisabledForTests = false;
private readonly int _senderCount;
private TelemetryClient? _client = null;
- private FrozenDictionary? _commonProperties = null;
+ private FrozenDictionary? _commonProperties = null;
private FrozenDictionary? _commonMeasurements = null;
private Task? _trackEventTask = null;
@@ -87,26 +87,31 @@ private static bool PermissionExists(IFirstTimeUseNoticeSentinel? sentinel)
return sentinel.Exists();
}
- public void TrackEvent(string eventName, IDictionary properties,
- IDictionary measurements)
+ public void TrackEvent(string? eventName, IDictionary? properties,
+ IDictionary? measurements)
{
if (!Enabled)
{
return;
}
- //continue the task in different threads
- if (_trackEventTask == null)
+ if (eventName is null)
{
- _trackEventTask = Task.Run(() => TrackEventTask(eventName, properties, measurements));
return;
}
- else
- {
- _trackEventTask = _trackEventTask.ContinueWith(
- x => TrackEventTask(eventName, properties, measurements)
- );
- }
+
+ //continue the task in different threads
+ if (_trackEventTask == null)
+ {
+ _trackEventTask = Task.Run(() => TrackEventTask(eventName, properties, measurements));
+ return;
+ }
+ else
+ {
+ _trackEventTask = _trackEventTask.ContinueWith(
+ x => TrackEventTask(eventName, properties, measurements)
+ );
+ }
}
public void Flush()
@@ -129,12 +134,16 @@ public void Dispose()
}
}
- public void ThreadBlockingTrackEvent(string eventName, IDictionary properties, IDictionary measurements)
+ public void ThreadBlockingTrackEvent(string? eventName, IDictionary? properties, IDictionary? measurements)
{
if (!Enabled)
{
return;
}
+ if (eventName is null)
+ {
+ return;
+ }
TrackEventTask(eventName, properties, measurements);
}
@@ -167,8 +176,8 @@ private void InitializeTelemetry()
private void TrackEventTask(
string eventName,
- IDictionary properties,
- IDictionary measurements)
+ IDictionary? properties,
+ IDictionary? measurements)
{
if (_client == null)
{
@@ -180,7 +189,7 @@ private void TrackEventTask(
var eventProperties = GetEventProperties(properties);
var eventMeasurements = GetEventMeasures(measurements);
- eventProperties ??= new Dictionary();
+ eventProperties ??= new Dictionary();
eventProperties.Add("event id", Guid.NewGuid().ToString());
_client.TrackEvent(PrependProducerNamespace(eventName), eventProperties, eventMeasurements);
@@ -194,7 +203,7 @@ private void TrackEventTask(
private static ActivityEvent CreateActivityEvent(
string eventName,
- IDictionary? properties,
+ IDictionary? properties,
IDictionary? measurements)
{
var tags = MakeTags(properties, measurements);
@@ -204,7 +213,7 @@ private static ActivityEvent CreateActivityEvent(
}
private static ActivityTagsCollection? MakeTags(
- IDictionary? properties,
+ IDictionary? properties,
IDictionary? measurements)
{
if (properties == null && measurements == null)
@@ -236,12 +245,12 @@ private static ActivityEvent CreateActivityEvent(
};
}
- private IDictionary? GetEventProperties(IDictionary? properties)
+ private IDictionary? GetEventProperties(IDictionary? properties)
{
return (properties, _commonProperties) switch
{
(null, null) => null,
- (null, not null) => _commonProperties == FrozenDictionary.Empty ? null : new Dictionary(_commonProperties),
+ (null, not null) => _commonProperties == FrozenDictionary.Empty ? null : new Dictionary(_commonProperties),
(not null, null) => properties,
(not null, not null) => Combine(_commonProperties, properties),
};
diff --git a/test/dotnet.Tests/TelemetryCommandTest.cs b/test/dotnet.Tests/TelemetryCommandTest.cs
index bda1eaf6e3b2..42ab6e53d0e3 100644
--- a/test/dotnet.Tests/TelemetryCommandTest.cs
+++ b/test/dotnet.Tests/TelemetryCommandTest.cs
@@ -56,15 +56,11 @@ public void TopLevelCommandNameShouldBeSentToTelemetry()
public void TopLevelCommandNameShouldBeSentToTelemetryWithPerformanceData()
{
string[] args = { "help" };
- Cli.Program.ProcessArgs(args, new TimeSpan(12345));
+ Cli.Program.ProcessArgs(args);
_fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == "toplevelparser/command" &&
e.Properties.ContainsKey("verb") &&
- e.Properties["verb"] == Sha256Hasher.Hash("HELP") &&
- e.Measurement.ContainsKey("Startup Time") &&
- e.Measurement["Startup Time"] == 1.2345 &&
- e.Measurement.ContainsKey("Parse Time") &&
- e.Measurement["Parse Time"] > 0);
+ e.Properties["verb"] == Sha256Hasher.Hash("HELP"));
}
[Fact]
@@ -75,24 +71,7 @@ public void TopLevelCommandNameShouldBeSentToTelemetryWithoutStartupTime()
_fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == "toplevelparser/command" &&
e.Properties.ContainsKey("verb") &&
- e.Properties["verb"] == Sha256Hasher.Hash("HELP") &&
- !e.Measurement.ContainsKey("Startup Time") &&
- e.Measurement.ContainsKey("Parse Time") &&
- e.Measurement["Parse Time"] > 0);
- }
-
- [Fact]
- public void TopLevelCommandNameShouldBeSentToTelemetryZeroStartupTime()
- {
- string[] args = { "help" };
- Cli.Program.ProcessArgs(args, new TimeSpan(0));
-
- _fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == "toplevelparser/command" &&
- e.Properties.ContainsKey("verb") &&
- e.Properties["verb"] == Sha256Hasher.Hash("HELP") &&
- !e.Measurement.ContainsKey("Startup Time") &&
- e.Measurement.ContainsKey("Parse Time") &&
- e.Measurement["Parse Time"] > 0);
+ e.Properties["verb"] == Sha256Hasher.Hash("HELP"));
}
[Fact]
@@ -110,25 +89,6 @@ public void DotnetNewCommandFirstArgumentShouldBeSentToTelemetry()
e.Properties["verb"] == Sha256Hasher.Hash("NEW"));
}
- [Fact(Skip = "https://github.com/dotnet/sdk/issues/24190")]
- public void DotnetNewCommandFirstArgumentShouldBeSentToTelemetryWithPerformanceData()
- {
- const string argumentToSend = "console";
- string[] args = { "new", argumentToSend };
- Cli.Program.ProcessArgs(args, new TimeSpan(23456));
- _fakeTelemetry
- .LogEntries.Should()
- .Contain(e => e.EventName == "sublevelparser/command" &&
- e.Properties.ContainsKey("argument") &&
- e.Properties["argument"] == Sha256Hasher.Hash(argumentToSend.ToUpper()) &&
- e.Properties.ContainsKey("verb") &&
- e.Properties["verb"] == Sha256Hasher.Hash("NEW") &&
- e.Measurement.ContainsKey("Startup Time") &&
- e.Measurement["Startup Time"] == 2.3456 &&
- e.Measurement.ContainsKey("Parse Time") &&
- e.Measurement["Parse Time"] > 0);
- }
-
[Fact]
public void DotnetHelpCommandFirstArgumentShouldBeSentToTelemetry()
{
@@ -266,26 +226,6 @@ public void AnyDotnetCommandVerbosityOpinionShouldBeSentToTelemetry()
e.Properties["verb"] == Sha256Hasher.Hash("RESTORE"));
}
- [Fact]
- public void AnyDotnetCommandVerbosityOpinionShouldBeSentToTelemetryWithPerformanceData()
- {
- const string optionKey = "verbosity";
- const string optionValueToSend = "minimal";
- string[] args = { "restore", "--" + optionKey, optionValueToSend };
- Cli.Program.ProcessArgs(args, new TimeSpan(34567));
- _fakeTelemetry
- .LogEntries.Should()
- .Contain(e => e.EventName == "sublevelparser/command" &&
- e.Properties.ContainsKey(optionKey) &&
- e.Properties[optionKey] == Sha256Hasher.Hash(optionValueToSend.ToUpper()) &&
- e.Properties.ContainsKey("verb") &&
- e.Properties["verb"] == Sha256Hasher.Hash("RESTORE") &&
- e.Measurement.ContainsKey("Startup Time") &&
- e.Measurement["Startup Time"] == 3.4567 &&
- e.Measurement.ContainsKey("Parse Time") &&
- e.Measurement["Parse Time"] > 0);
- }
-
[Fact]
public void DotnetBuildAndPublishCommandOpinionsShouldBeSentToTelemetry()
{