From ecab80a3a5ae10c024379be8fa3311e4169a333f Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Tue, 10 Nov 2020 10:49:56 -0800 Subject: [PATCH 01/15] Prefix the environment variable "AzPredictor" --- tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs index 4acf2c409a50..01effa836f43 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs @@ -137,7 +137,7 @@ private void OverrideSettingsFromProfile() private void OverrideSettingsFromEnv() { - var serviceUri = System.Environment.GetEnvironmentVariable("ServiceUri"); + var serviceUri = System.Environment.GetEnvironmentVariable("AzPredictorServiceUri"); if (!string.IsNullOrWhiteSpace(serviceUri)) { From ae6f18e53449c02eefe14e3f4a8f0e2283402204 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Tue, 17 Nov 2020 10:33:21 -0800 Subject: [PATCH 02/15] Refactor the code - Improve the comment and its format. - Create a concret class type to replace Tuple and ValueTuple. - Verify method parameter values. --- .../Az.Tools.Predictor/AzContext.cs | 4 +- .../Az.Tools.Predictor/AzPredictor.cs | 96 ++++----- .../Az.Tools.Predictor/AzPredictorService.cs | 203 +++++++++++------- .../AzPredictorTelemetryClient.cs | 28 +-- .../{Prediction.cs => CommandLine.cs} | 33 ++- .../{Predictor.cs => CommandLinePredictor.cs} | 169 ++++++++------- .../CommandLineSuggestion.cs | 102 +++++++++ .../Az.Tools.Predictor/IAzPredictorService.cs | 13 +- .../Az.Tools.Predictor/ITelemetryClient.cs | 15 +- .../Az.Tools.Predictor/Parameter.cs | 48 +++++ .../Az.Tools.Predictor/ParameterSet.cs | 23 +- .../Az.Tools.Predictor/Settings.cs | 8 +- ...redictionSource.cs => SuggestionSource.cs} | 10 +- .../Utilities/Validation.cs | 77 +++++++ ...osoft.PowerShell.PSReadLine.Polyfiller.dll | Bin 4608 -> 13704 bytes .../Microsoft.PowerShell.PSReadLine2.dll | Bin 313344 -> 298376 bytes 16 files changed, 552 insertions(+), 277 deletions(-) rename tools/Az.Tools.Predictor/Az.Tools.Predictor/{Prediction.cs => CommandLine.cs} (52%) rename tools/Az.Tools.Predictor/Az.Tools.Predictor/{Predictor.cs => CommandLinePredictor.cs} (61%) create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLineSuggestion.cs create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Parameter.cs rename tools/Az.Tools.Predictor/Az.Tools.Predictor/{PredictionSource.cs => SuggestionSource.cs} (78%) create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Validation.cs diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzContext.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzContext.cs index b2e929c5be71..a363280c66be 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzContext.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzContext.cs @@ -171,9 +171,9 @@ private static string GenerateSha256HashString(string originInput) } /// - /// Get the MAC address of the default NIC, or null if none can be found + /// Get the MAC address of the default NIC, or null if none can be found. /// - /// The MAC address of the defautl nic, or null if none is found + /// The MAC address of the defautl nic, or null if none is found. private static string GetMACAddress() { return NetworkInterface.GetAllNetworkInterfaces()? diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs index dd5be6e10b80..c8c6017fc7e1 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------- // // Copyright Microsoft Corporation // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,7 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { /// - /// The implementation of a to provide suggestion in PSReadLine. + /// The implementation of a to provide suggestions in PSReadLine. /// internal sealed class AzPredictor : ICommandPredictor { @@ -61,29 +61,31 @@ internal sealed class AzPredictor : ICommandPredictor private Queue _lastTwoMaskedCommands = new Queue(AzPredictorConstants.CommandHistoryCountToProcess); - // This contains the user modified texts and the original suggestion. + /// + /// the adjusted texts and the source text for the suggestion. + /// private Dictionary _userAcceptedAndSuggestion = new Dictionary(); /// - /// Constructs a new instance of + /// Constructs a new instance of . /// - /// The service that provides the suggestion - /// The client to collect telemetry - /// The settings of the service - /// The Az context which this module runs with + /// The service that provides the suggestion. + /// The client to collect telemetry. + /// The settings for . + /// The Az context which this module runs in. public AzPredictor(IAzPredictorService service, ITelemetryClient telemetryClient, Settings settings, IAzContext azContext) { - this._service = service; - this._telemetryClient = telemetryClient; - this._settings = settings; - this._azContext = azContext; + _service = service; + _telemetryClient = telemetryClient; + _settings = settings; + _azContext = azContext; } /// public void StartEarlyProcessing(IReadOnlyList history) { // The context only changes when the user executes the corresponding command. - this._azContext?.UpdateContext(); + _azContext?.UpdateContext(); lock (_userAcceptedAndSuggestion) { _userAcceptedAndSuggestion.Clear(); @@ -173,55 +175,47 @@ public void OnSuggestionAccepted(string acceptedSuggestion) /// public List GetSuggestion(PredictionContext context, CancellationToken cancellationToken) { - var localCancellationToken = Settings.ContinueOnTimeout ? CancellationToken.None : cancellationToken; - - IEnumerable> suggestions = Enumerable.Empty>(); - string maskedUserInput = string.Empty; - // This is the list of records of the source suggestion and the prediction source. - var telemetryData = new List>(); - - try + if (_settings.SuggestionCount.Value > 0) { - maskedUserInput = AzPredictor.MaskCommandLine(context.InputAst.FindAll((ast) => ast is CommandAst, true).LastOrDefault() as CommandAst); - - suggestions = _service.GetSuggestion(context.InputAst, _settings.SuggestionCount.Value, _settings.MaxAllowedCommandDuplicate.Value, localCancellationToken); + try + { + var localCancellationToken = Settings.ContinueOnTimeout ? CancellationToken.None : cancellationToken; + var maskedUserInput = AzPredictor.MaskCommandLine(context.InputAst.FindAll((ast) => ast is CommandAst, true).LastOrDefault() as CommandAst); + var suggestions = _service.GetSuggestion(context.InputAst, _settings.SuggestionCount.Value, _settings.MaxAllowedCommandDuplicate.Value, localCancellationToken); - localCancellationToken.ThrowIfCancellationRequested(); + localCancellationToken.ThrowIfCancellationRequested(); - var userAcceptedAndSuggestion = new Dictionary(); + var userAcceptedAndSuggestion = new Dictionary(); - foreach (var s in suggestions) - { - telemetryData.Add(ValueTuple.Create(s.Item2, s.Item3)); - userAcceptedAndSuggestion[s.Item1] = s.Item2; - } + for (int i = 0; i < suggestions.Count; ++i) + { + userAcceptedAndSuggestion[suggestions.PredictiveSuggestions[i].SuggestionText] = suggestions.SourceTexts[i]; + } - lock (_userAcceptedAndSuggestion) - { - foreach (var u in userAcceptedAndSuggestion) + lock (_userAcceptedAndSuggestion) { - _userAcceptedAndSuggestion[u.Key] = u.Value; + foreach (var u in userAcceptedAndSuggestion) + { + _userAcceptedAndSuggestion[u.Key] = u.Value; + } } - } - localCancellationToken.ThrowIfCancellationRequested(); + localCancellationToken.ThrowIfCancellationRequested(); - var returnedValue = suggestions.Select((r, index) => - { - return new PredictiveSuggestion(r.Item1); - }) - .ToList(); + var returnedValue = suggestions.PredictiveSuggestions.ToList(); - _telemetryClient.OnGetSuggestion(maskedUserInput, - telemetryData, - cancellationToken.IsCancellationRequested); + _telemetryClient.OnGetSuggestion(maskedUserInput, + suggestions.SourceTexts, + suggestions.SuggestionSources, + cancellationToken.IsCancellationRequested); - return returnedValue; + return returnedValue; - } - catch (Exception e) when (!(e is OperationCanceledException)) - { - this._telemetryClient.OnGetSuggestionError(e); + } + catch (Exception e) when (!(e is OperationCanceledException)) + { + _telemetryClient.OnGetSuggestionError(e); + } } return new List(); @@ -233,7 +227,7 @@ public List GetSuggestion(PredictionContext context, Cance /// them to the model. /// e.g., Get-AzContext -Name Hello -Location 'EastUS' => Get-AzContext -Location *** -Name *** /// - /// The last user input command + /// The last user input command. private static string MaskCommandLine(CommandAst cmdAst) { var commandElements = cmdAst?.CommandElements; diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index 6be87ae33847..f04edde6d725 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; @@ -27,7 +28,7 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { /// - /// A service that talk to Aladdin endpoints to get the commands and predictions. + /// A service that connects to Aladdin endpoints to get the model and provides suggestions to PSReadLine. /// internal class AzPredictorService : IAzPredictorService, IDisposable { @@ -48,7 +49,7 @@ public sealed class RequestContext public string ClientType { get; set; } = AzPredictorService.ClientType; public RequestContext Context { get; set; } = new RequestContext(); - public PredictionRequestBody(string command) => this.History = command; + public PredictionRequestBody(string command) => History = command; }; [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] @@ -61,10 +62,26 @@ private sealed class CommandRequestContext private readonly HttpClient _client; private readonly string _commandsEndpoint; private readonly string _predictionsEndpoint; - private volatile Tuple _commandSuggestions; // The command and the prediction for that. - private volatile Predictor _commands; - private volatile string _commandForPrediction; - private HashSet _commandSet; + + /// + /// The history command line and the predictor based on that. + /// + private volatile Tuple _commandBasedPredictor; + + /// + /// The predictor to used when doesn't return enough suggestions. + /// + private volatile CommandLinePredictor _fallbackPredictor; + + /// + /// The history command line that we request prediction for. + /// + private volatile string _commandToRequestPrediction; + + /// + /// All the command lines we can provide as suggestions. + /// + private HashSet _allPredictiveCommands; private CancellationTokenSource _predictionRequestCancellationSource; private readonly ParameterValuePredictor _parameterValuePredictor = new ParameterValuePredictor(); @@ -72,31 +89,33 @@ private sealed class CommandRequestContext private readonly IAzContext _azContext; /// - /// The AzPredictor service interacts with the Aladdin service specified in serviceUri. - /// At initialization, it requests a list of the popular commands. + /// Creates a new instance of . /// /// The URI of the Aladdin service. /// The telemetry client. - /// The Az context which this module runs with + /// The Az context which this module runs in. public AzPredictorService(string serviceUri, ITelemetryClient telemetryClient, IAzContext azContext) { - this._commandsEndpoint = $"{serviceUri}{AzPredictorConstants.CommandsEndpoint}?clientType={AzPredictorService.ClientType}&context={JsonConvert.SerializeObject(new CommandRequestContext())}"; - this._predictionsEndpoint = serviceUri + AzPredictorConstants.PredictionsEndpoint; - this._telemetryClient = telemetryClient; - this._azContext = azContext; + Validation.CheckArgument(!string.IsNullOrWhiteSpace(serviceUri), $"{nameof(serviceUri)} cannot be null or whitespace."); + Validation.CheckArgument(telemetryClient, $"{nameof(telemetryClient)} cannot be null."); + Validation.CheckArgument(azContext, $"{nameof(azContext)} cannot be null."); - this._client = new HttpClient(); - this._client.DefaultRequestHeaders?.Add(AzPredictorService.ThrottleByIdHeader, this._azContext.UserId); + _commandsEndpoint = $"{serviceUri}{AzPredictorConstants.CommandsEndpoint}?clientType={AzPredictorService.ClientType}&context={JsonConvert.SerializeObject(new CommandRequestContext())}"; + _predictionsEndpoint = serviceUri + AzPredictorConstants.PredictionsEndpoint; + _telemetryClient = telemetryClient; + _azContext = azContext; - RequestCommands(); + _client = new HttpClient(); + + RequestAllPredictiveCommands(); } /// - /// A default constructor for the derived class. + /// A default constructor for the derived class. This is used in test cases. /// protected AzPredictorService() { - RequestCommands(); + RequestAllPredictiveCommands(); } /// @@ -106,131 +125,144 @@ public void Dispose() } /// - /// Dispose the object + /// Dispose the object. /// - /// Indicate if this is called from + /// Indicate if this is called from . protected virtual void Dispose(bool disposing) { if (disposing) { - if (this._predictionRequestCancellationSource != null) + if (_predictionRequestCancellationSource != null) { - this._predictionRequestCancellationSource.Dispose(); - this._predictionRequestCancellationSource = null; + _predictionRequestCancellationSource.Dispose(); + _predictionRequestCancellationSource = null; } } } /// /// - /// Queries the Predictor with the user input if predictions are available, otherwise uses commands + /// Tries to get the suggestions for the user input from the command history. If that doesn't find + /// suggestions, it'll fallback to find the suggestion regardless of command history. /// - public IEnumerable> GetSuggestion(Ast input, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken) + public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken) { - var commandSuggestions = this._commandSuggestions; - var command = this._commandForPrediction; + Validation.CheckArgument(input, $"{nameof(input)} cannot be null"); + Validation.CheckArgument(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0."); - IList> results = new List>(); - var presentCommands = new System.Collections.Generic.Dictionary(); - var resultsFromSuggestionTuple = commandSuggestions?.Item2?.Query(input, presentCommands, suggestionCount, maxAllowedCommandDuplicate, cancellationToken); - var resultsFromSuggestion = resultsFromSuggestionTuple.Item1; - presentCommands = resultsFromSuggestionTuple.Item2.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + if (suggestionCount == 0) + { + return null; + } + var commandBasedPredictor = _commandBasedPredictor; + var command = _commandToRequestPrediction; - if (resultsFromSuggestion != null) + var presentCommands = new System.Collections.Generic.Dictionary(); + var resultsFromSuggestionTuple = commandBasedPredictor?.Item2?.GetSuggestion(input, presentCommands, suggestionCount, cancellationToken); + var result = resultsFromSuggestionTuple.Item1; + presentCommands = resultsFromSuggestionTuple.Item2.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + if ((result != null) && (result.Count > 0)) { - var predictionSource = PredictionSource.None; + var suggestionSource = SuggestionSource.None; - if (string.Equals(command, commandSuggestions?.Item1, StringComparison.Ordinal)) + if (string.Equals(command, commandBasedPredictor?.Item1, StringComparison.Ordinal)) { - predictionSource = PredictionSource.CurrentCommand; + suggestionSource = SuggestionSource.CurrentCommand; } else { - predictionSource = PredictionSource.PreviousCommand; + suggestionSource = SuggestionSource.PreviousCommand; } - if (resultsFromSuggestion != null) + for (var i = 0; i < result.Count; ++i) { - foreach (var r in resultsFromSuggestion) - { - results.Add(ValueTuple.Create(r.Key, r.Value, predictionSource)); - } + result.UpdateSuggestionSource(i, suggestionSource); } } - if ((resultsFromSuggestion == null) || (resultsFromSuggestion.Count() < suggestionCount)) + if ((result == null) || (result.Count < suggestionCount)) { - var commands = this._commands; - var resultsFromCommandsTuple = commands?.Query(input, presentCommands,suggestionCount - resultsFromSuggestion.Count(), maxAllowedCommandDuplicate, cancellationToken); - var resultsFromCommands = resultsFromCommandsTuple.Item1; - presentCommands = resultsFromCommandsTuple.Item2.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + var fallbackPredictor = _fallbackPredictor; + var resultsFromFallbackTuple = fallbackPredictor?.GetSuggestion(input, presentCommands, suggestionCount - result.Count, maxAllowedCommandDuplicate, cancellationToken); + var resultsFromFallback = resultsFromCommandsTuple.Item1; + presentCommands = resultsFromFallbackTuple.Item2.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - if (resultsFromCommands != null) + if (result == null) { - foreach (var r in resultsFromCommands) + result = resultsFromFallback; + } + else if ((resultsFromFallback != null) && (resultsFromFallback.Count > 0)) + { + for (var i = 0; i < resultsFromFallback.Count; ++i) { - if (resultsFromSuggestion?.ContainsKey(r.Key) == true) + if (result.SourceTexts.Contains(resultsFromFallback.SourceTexts[i])) { continue; } - results.Add(ValueTuple.Create(r.Key, r.Value, PredictionSource.StaticCommands)); + result.AddSuggestion(resultsFromFallback.PredictiveSuggestions[i], resultsFromFallback.SourceTexts[i], resultsFromFallback.SuggestionSources[i]); } } } - return results; + return result; } /// public virtual void RequestPredictions(IEnumerable commands) { - AzPredictorService.ReplaceThrottleUserIdToHeader(this._client?.DefaultRequestHeaders, this._azContext.UserId); + Validation.CheckArgument(commands, $"{nameof(commands)} cannot be null."); + var localCommands= string.Join(AzPredictorConstants.CommandConcatenator, commands); - this._telemetryClient.OnRequestPrediction(localCommands); + _telemetryClient.OnRequestPrediction(localCommands); - if (string.Equals(localCommands, this._commandForPrediction, StringComparison.Ordinal)) + if (string.Equals(localCommands, _commandToRequestPrediction, StringComparison.Ordinal)) { // It's the same history we've already requested the prediction for last time, skip it. return; } - else + + if (commands.Any()) { - this.SetPredictionCommand(localCommands); + SetCommandToRequestPrediction(localCommands); // When it's called multiple times, we only need to keep the one for the latest command. - this._predictionRequestCancellationSource?.Cancel(); - this._predictionRequestCancellationSource = new CancellationTokenSource(); + _predictionRequestCancellationSource?.Cancel(); + _predictionRequestCancellationSource = new CancellationTokenSource(); - var cancellationToken = this._predictionRequestCancellationSource.Token; + var cancellationToken = _predictionRequestCancellationSource.Token; // We don't need to block on the task. We send the HTTP request and update prediction list at the background. Task.Run(async () => { try { + AzPredictorService.ReplaceThrottleUserIdToHeader(_client?.DefaultRequestHeaders, _azContext.UserId); + var requestContext = new PredictionRequestBody.RequestContext() { - SessionId = this._telemetryClient.SessionId, - CorrelationId = this._telemetryClient.CorrelationId, + SessionId = _telemetryClient.SessionId, + CorrelationId = _telemetryClient.CorrelationId, }; + var requestBody = new PredictionRequestBody(localCommands) { Context = requestContext, }; var requestBodyString = JsonConvert.SerializeObject(requestBody); - var httpResponseMessage = await _client.PostAsync(this._predictionsEndpoint, new StringContent(requestBodyString, Encoding.UTF8, "application/json"), cancellationToken); + var httpResponseMessage = await _client.PostAsync(_predictionsEndpoint, new StringContent(requestBodyString, Encoding.UTF8, "application/json"), cancellationToken); var reply = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); var suggestionsList = JsonConvert.DeserializeObject>(reply); - this.SetSuggestionPredictor(localCommands, suggestionsList); + SetCommandBasedPreditor(localCommands, suggestionsList); } catch (Exception e) when (!(e is OperationCanceledException)) { - this._telemetryClient.OnRequestPredictionError(localCommands, e); + _telemetryClient.OnRequestPredictionError(localCommands, e); } }, cancellationToken); @@ -240,26 +272,30 @@ public virtual void RequestPredictions(IEnumerable commands) /// public virtual void RecordHistory(CommandAst history) { - this._parameterValuePredictor.ProcessHistoryCommand(history); + Validation.CheckArgument(history, $"{nameof(history)} cannot be null."); + + _parameterValuePredictor.ProcessHistoryCommand(history); } /// - public bool IsSupportedCommand(string cmd) => !string.IsNullOrWhiteSpace(cmd) && (_commandSet?.Contains(cmd) == true); + public bool IsSupportedCommand(string cmd) => !string.IsNullOrWhiteSpace(cmd) && (_allPredictiveCommands?.Contains(cmd) == true); /// /// Requests a list of popular commands from service. These commands are used as fallback suggestion /// if none of the predictions fit for the current input. This method should be called once per session. /// - protected virtual void RequestCommands() + protected virtual void RequestAllPredictiveCommands() { // We don't need to block on the task. We send the HTTP request and update commands and predictions list at the background. Task.Run(async () => { - var httpResponseMessage = await this._client.GetAsync(this._commandsEndpoint); + _client.DefaultRequestHeaders?.Add(AzPredictorService.ThrottleByIdHeader, _azContext.UserId); + + var httpResponseMessage = await _client.GetAsync(_commandsEndpoint); var reply = await httpResponseMessage.Content.ReadAsStringAsync(); - var commands_reply = JsonConvert.DeserializeObject>(reply); - this.SetCommandsPredictor(commands_reply); + var commandsReply = JsonConvert.DeserializeObject>(reply); + SetFallbackPredictor(commandsReply); // Initialize predictions RequestPredictions(new string[] { @@ -269,32 +305,39 @@ protected virtual void RequestCommands() } /// - /// Sets the commands predictor. + /// Sets the fallback predictor. /// /// The command collection to set the predictor - protected void SetCommandsPredictor(IList commands) + protected void SetFallbackPredictor(IList commands) { - this._commands = new Predictor(commands, this._parameterValuePredictor); - this._commandSet = commands.Select(x => AzPredictorService.GetCommandName(x)).ToHashSet(StringComparer.OrdinalIgnoreCase); // this could be slow + Validation.CheckArgument(commands, $"{nameof(commands)} cannot be null."); + + _fallbackPredictor = new CommandLinePredictor(commands, _parameterValuePredictor); + _allPredictiveCommands = commands.Select(x => AzPredictorService.GetCommandName(x)).ToHashSet(StringComparer.OrdinalIgnoreCase); // this could be slow } /// - /// Sets the suggestiosn predictor. + /// Sets the predictor based on the command history. /// /// The commands that the suggestions are for /// The suggestion collection to set the predictor - protected void SetSuggestionPredictor(string commands, IList suggestions) + protected void SetCommandBasedPreditor(string commands, IList suggestions) { - this._commandSuggestions = Tuple.Create(commands, new Predictor(suggestions, this._parameterValuePredictor)); + Validation.CheckArgument(!string.IsNullOrWhiteSpace(commands), $"{nameof(commands)} cannot be null or whitespace."); + Validation.CheckArgument(suggestions, $"{nameof(suggestions)} cannot be null."); + + _commandBasedPredictor = Tuple.Create(commands, new CommandLinePredictor(suggestions, _parameterValuePredictor)); } /// /// Updates the command for prediction. /// /// The command for the new prediction - protected void SetPredictionCommand(string command) + protected void SetCommandToRequestPrediction(string command) { - this._commandForPrediction = command; + Validation.CheckArgument(!string.IsNullOrWhiteSpace(command), $"{nameof(command)} cannot be null or whitespace."); + + _commandToRequestPrediction = command; } /// diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs index d8c627a43202..cdc725d0bed9 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs @@ -24,7 +24,7 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { /// - /// A telemetry client implementation to collect the telemetry data for AzPredictor + /// A telemetry client implementation to collect the telemetry data for AzPredictor. /// sealed class AzPredictorTelemetryClient : ITelemetryClient { @@ -41,9 +41,9 @@ sealed class AzPredictorTelemetryClient : ITelemetryClient private Tuple, string> _cachedAzModulesVersions = Tuple.Create, string>(null, null); /// - /// Constructs a new instance of + /// Constructs a new instance of . /// - /// The Az context which this module runs with + /// The Az context which this module runs with. public AzPredictorTelemetryClient(IAzContext azContext) { TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault(); @@ -69,7 +69,7 @@ public void OnHistory(string historyLine) _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/CommandHistory", properties); #if TELEMETRY_TRACE && DEBUG - Console.WriteLine("Recording CommandHistory"); + System.Diagnostics.Trace.WriteLine("Recording CommandHistory"); #endif } @@ -89,7 +89,7 @@ public void OnRequestPrediction(string command) _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/RequestPrediction", properties); #if TELEMETRY_TRACE && DEBUG - Console.WriteLine("Recording RequestPrediction"); + System.Diagnostics.Trace.WriteLine("Recording RequestPrediction"); #endif } @@ -108,7 +108,7 @@ public void OnRequestPredictionError(string command, Exception e) _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/RequestPredictionError", properties); #if TELEMETRY_TRACE && DEBUG - Console.WriteLine("Recording RequestPredictionError"); + System.Diagnostics.Trace.WriteLine("Recording RequestPredictionError"); #endif } @@ -126,12 +126,12 @@ public void OnSuggestionAccepted(string acceptedSuggestion) _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/AcceptSuggestion", properties); #if TELEMETRY_TRACE && DEBUG - Console.WriteLine("Recording AcceptSuggestion"); + System.Diagnostics.Trace.WriteLine("Recording AcceptSuggestion"); #endif } /// - public void OnGetSuggestion(string maskedUserInput, IEnumerable> suggestions, bool isCancelled) + public void OnGetSuggestion(string maskedUserInput, IEnumerable suggestions, IEnumerable suggestionSource, bool isCancelled) { if (!IsDataCollectionAllowed()) { @@ -140,13 +140,13 @@ public void OnGetSuggestion(string maskedUserInput, IEnumerable ValueTuple.Create(s.First, s.Second)))); properties.Add("IsCancelled", isCancelled.ToString(CultureInfo.InvariantCulture)); _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/GetSuggestion", properties); #if TELEMETRY_TRACE && DEBUG - Console.WriteLine("Recording GetSuggestion"); + System.Diagnostics.Trace.WriteLine("Recording GetSuggestion"); #endif } @@ -164,15 +164,15 @@ public void OnGetSuggestionError(Exception e) _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/GetSuggestionError", properties); #if TELEMETRY_TRACE && DEBUG - Console.WriteLine("Recording GetSuggestioinError"); + System.Diagnostics.Trace.WriteLine("Recording GetSuggestioinError"); #endif } /// - /// Check whether the data collection is opted in from user + /// Check whether the data collection is opted in from user. /// - /// true if allowed - private bool IsDataCollectionAllowed() + /// true if allowed. + private static bool IsDataCollectionAllowed() { if (AzurePSDataCollectionProfile.Instance.EnableAzureDataCollection == true) { diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Prediction.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLine.cs similarity index 52% rename from tools/Az.Tools.Predictor/Az.Tools.Predictor/Prediction.cs rename to tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLine.cs index da065032efc1..991249765eb0 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Prediction.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLine.cs @@ -12,38 +12,37 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System.Collections.Generic; +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { /// - /// A prediction candidate consists of the command name and list of parameter sets, - /// where each parameter set is a set of parameters (order independent) that go along with the command. + /// A command line consists of the command name and the parameter set, + /// where the parameter set is a set of parameters (order independent) that go along with the command. /// - sealed class Prediction + sealed class CommandLine { /// - /// Gets the command name + /// Gets the command name. /// - public string Command { get; } + public string Name { get; } /// - /// Gets the list of + /// Gets the . /// - public IList ParameterSets { get; } + public ParameterSet ParameterSet { get; } /// - /// Create a new instance of with the command and parameter set. + /// Create a new instance of with the command name and parameter set. /// - /// The command name - /// The parameter set - public Prediction(string command, ParameterSet parameters) + /// The command name. + /// The parameter set. + public CommandLine(string name, ParameterSet parameterSet) { - this.Command = command; - ParameterSets = new List - { - parameters - }; + Validation.CheckArgument(!string.IsNullOrWhiteSpace(name), $"{nameof(name)} must not be null or whitespace."); + + Name = name; + ParameterSet = parameterSet; } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Predictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs similarity index 61% rename from tools/Az.Tools.Predictor/Az.Tools.Predictor/Predictor.cs rename to tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index 3c85c1c8e602..00efca93ec65 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Predictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.Collections.Generic; using System.Linq; @@ -22,22 +23,29 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { /// - /// Caches predictions from Aladdin service, queries user input, e.g. "Connec" and returns autocompleted version, or null. + /// This class query the command model from Aladdin service and return suggestions based on user input, for example, + /// when the user inputs "Connec", it returns "Connect-AzAccount". /// - internal sealed class Predictor + /// + /// The suggestion returned to PSReadLine may not be the same as the model to generate the suggestion. The suggestion may + /// be adjusted based on user input. + /// + internal sealed class CommandLinePredictor { - private readonly IList _predictions; + private readonly IList _commandLinePredictions = new List(); private readonly ParameterValuePredictor _parameterValuePredictor; /// - /// Predictor must be initialized with a list of string suggestions. + /// Creates a new instance of . /// - /// List of suggestions from the model, sorted by frequency (most to least) + /// List of suggestions from the model, sorted by frequency (most to least). /// Provide the prediction to the parameter values. - public Predictor(IList modelPredictions, ParameterValuePredictor parameterValuePredictor) + public CommandLinePredictor(IList modelPredictions, ParameterValuePredictor parameterValuePredictor) { - this._parameterValuePredictor = parameterValuePredictor; - this._predictions = new List(); + Validation.CheckArgument(modelPredictions, $"{nameof(modelPredictions)} cannot be null."); + + _parameterValuePredictor = parameterValuePredictor; + var commnadLines = new List(); foreach (var predictionTextRaw in modelPredictions ?? Enumerable.Empty()) { @@ -48,24 +56,26 @@ public Predictor(IList modelPredictions, ParameterValuePredictor paramet if (commandAst?.CommandElements[0] is StringConstantExpressionAst commandName) { var parameterSet = new ParameterSet(commandAst); - this._predictions.Add(new Prediction(commandName.Value, parameterSet)); + this._commandLinePredictions.Add(new CommandLine(commandName.Value, parameterSet)); } } } /// - /// Given a user input PowerShell AST, returns prediction text. + /// Returns suggestions given the user input. /// /// PowerShell AST input of the user, generated by PSReadLine /// Commands already present. - /// The number of suggestion to return. + /// The number of suggestions to return. /// The maximum amount of the same commnds in the list of predictions. /// The cancellation token - /// The collection of suggestions. The key is the predicted text adjusted based on . The - /// value is the original text to create the adjusted text. - public Tuple, IDictionary> Query(Ast input, IDictionary presentCommands, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken) + /// The collections of suggestions and related information. + public Tuple> GetSuggestion(Ast input, IDictionary presentCommands, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken) { - if (suggestionCount <= 0) + Validation.CheckArgument(input, $"{nameof(input)} cannot but null."); + Validation.CheckArgument(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0."); + + if (suggestionCount == 0) { return null; } @@ -78,14 +88,13 @@ public Tuple, IDictionary> Query(Ast in return null; } - var results = new Dictionary(StringComparer.OrdinalIgnoreCase); + CommandLineSuggestion result = new(); var resultsTemp = new Dictionary(StringComparer.OrdinalIgnoreCase); try { var inputParameterSet = new ParameterSet(commandAst); - var isCommandNameComplete = (((commandAst?.CommandElements != null) && (commandAst.CommandElements.Count > 1)) || ((input as ScriptBlockAst)?.Extent?.Text?.EndsWith(' ') == true)); Func commandNameQuery = (command) => command.Equals(commandName, StringComparison.OrdinalIgnoreCase); @@ -105,86 +114,84 @@ public Tuple, IDictionary> Query(Ast in var resultBuilder = new StringBuilder(); var usedParams = new HashSet(); var sourceBuilder = new StringBuilder(); - - for (var i = 0; i < _predictions.Count && results.Count < suggestionCount; ++i) + + for (var i = 0; i < _commandLinePredictions.Count && result.Count < suggestionCount; ++i) { - if (commandNameQuery(_predictions[i].Command)) + if (commandNameQuery(_commandLinePredictions[i].Name)) { - foreach (var parameterSet in _predictions[i].ParameterSets) - { - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); + + resultBuilder.Clear(); + resultBuilder.Append(_commandLinePredictions[i].Name); + usedParams.Clear(); - resultBuilder.Clear(); - resultBuilder.Append(_predictions[i].Command); - usedParams.Clear(); + if (DoesPredictionParameterSetMatchInput(resultBuilder, inputParameterSet, _commandLinePredictions[i].ParameterSet, usedParams)) + { + PredictRestOfParameters(resultBuilder, _commandLinePredictions[i].ParameterSet.Parameters, usedParams); + var prediction = UnescapePredictionText(resultBuilder); - if (DoesPredictionParameterSetMatchInput(resultBuilder, inputParameterSet, parameterSet, usedParams)) + if (prediction.Length <= input.Extent.Text.Length) { - PredictRestOfParameters(resultBuilder, parameterSet.Parameters, usedParams); - var prediction = UnescapePredictionText(resultBuilder); + continue; + } - if (prediction.Length <= input.Extent.Text.Length) - { - continue; - } + sourceBuilder.Clear(); + sourceBuilder.Append(_commandLinePredictions[i].Name); - sourceBuilder.Clear(); - sourceBuilder.Append(_predictions[i].Command); + foreach (var p in _commandLinePredictions[i].ParameterSet.Parameters) + { + _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); + _ = sourceBuilder.Append(p.Name); - foreach (var p in parameterSet.Parameters) + if (!string.IsNullOrWhiteSpace(p.Value)) { _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = sourceBuilder.Append(p.Item1); - - if (!string.IsNullOrWhiteSpace(p.Item2)) - { - _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = sourceBuilder.Append(p.Item2); - } + _ = sourceBuilder.Append(p.Name); } + } - if (!presentCommands.ContainsKey(_predictions[i].Command)) - { - results.Add(prediction.ToString(), sourceBuilder.ToString()); - presentCommands.Add(_predictions[i].Command, 1); - } - else if (presentCommands[_predictions[i].Command] < maxAllowedCommandDuplicate) - { - results.Add(prediction.ToString(), sourceBuilder.ToString()); - presentCommands[_predictions[i].Command] += 1; - } - else - { - resultsTemp.Add(prediction.ToString(), sourceBuilder.ToString()); - } + if (!presentCommands.ContainsKey(_commandLinePredictions[i].Name)) + { + result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); + presentCommands.Add(_commandLinePredictions[i].Name, 1); + } + else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDupl) + { + result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); + presentCommands[_commandLinePredictions[i].Name] += 1; + } + else + { + resultsTemp.Add(prediction.ToString(), sourceBuilder.ToString()); + } - if (results.Count == suggestionCount) - { - break; - } + if (result.Count == suggestionCount) + { + break; } } } } } catch - { + { } - if ((results.Count < suggestionCount) && (resultsTemp.Count >0)) + if ((result.Count < suggestionCount) && (resultsTemp.Count > 0)) { - resultsTemp.ToList().GetRange(0, suggestionCount - results.Count).ForEach(x => results.Add(x.Key,x.Value)); + resultsTemp.ToList().GetRange(0, suggestionCount - result.Count).ForEach(x => result.AddSuggestion(new PredictiveSuggestion(x.Key), x.Value)); } - return new Tuple, IDictionary>(results, presentCommands); + + return new Tuple>(result, presentCommands); } /// /// Appends unused parameters to the builder. /// - /// StringBuilder that aggregates the prediction text output + /// StringBuilder that aggregates the prediction text output. /// Chosen prediction parameters. /// Set of used parameters for set. - private void PredictRestOfParameters(StringBuilder builder, IList> parameters, HashSet usedParams) + private void PredictRestOfParameters(StringBuilder builder, IReadOnlyList parameters, HashSet usedParams) { for (var j = 0; j < parameters.Count; j++) { @@ -198,8 +205,8 @@ private void PredictRestOfParameters(StringBuilder builder, IList /// Determines if parameter set contains all of the parameters of the input. /// - /// StringBuilder that aggregates the prediction text output - /// Parsed ParameterSet from the user input AST + /// StringBuilder that aggregates the prediction text output. + /// Parsed ParameterSet from the user input AST. /// Candidate prediction parameter set. /// Set of used parameters for set. private bool DoesPredictionParameterSetMatchInput(StringBuilder builder, ParameterSet inputParameters, ParameterSet predictionParameters, HashSet usedParams) @@ -214,13 +221,13 @@ private bool DoesPredictionParameterSetMatchInput(StringBuilder builder, Paramet else { usedParams.Add(matchIndex); - if (inputParameter.Item2 != null) + if (inputParameter.Value != null) { _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = builder.Append(predictionParameters.Parameters[matchIndex].Item1); + _ = builder.Append(predictionParameters.Parameters[matchIndex].Name); _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = builder.Append(inputParameter.Item2); + _ = builder.Append(inputParameter.Value); } else { @@ -242,10 +249,10 @@ private bool DoesPredictionParameterSetMatchInput(StringBuilder builder, Paramet /// "TestVM" is predicted for Get-AzVM. /// /// The string builder to create the whole predicted command line. - /// The parameter name and vlaue from prediction - private void BuildParameterValue(StringBuilder builder, Tuple parameter) + /// The parameter name and value from prediction. + private void BuildParameterValue(StringBuilder builder, Parameter parameter) { - var parameterName = parameter.Item1; + var parameterName = parameter.Name; _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); _ = builder.Append(parameterName); @@ -253,7 +260,7 @@ private void BuildParameterValue(StringBuilder builder, Tuple pa if (string.IsNullOrWhiteSpace(parameterValue)) { - parameterValue = parameter.Item2; + parameterValue = parameter.Value; } if (!string.IsNullOrWhiteSpace(parameterValue)) @@ -266,14 +273,14 @@ private void BuildParameterValue(StringBuilder builder, Tuple pa /// /// Determines the index of the given parameter in the parameter set. /// - /// A tuple, parameter AST, and argument AST (or null), representing the parameter. + /// The parameter name and its value. /// Prediction parameter setto find parameter position in. /// Set of used parameters for set. - private static int FindParameterPositionInSet(Tuple parameter, ParameterSet predictionSet, HashSet usedParams) + private static int FindParameterPositionInSet(Parameter parameter, ParameterSet predictionSet, HashSet usedParams) { for (var k = 0; k < predictionSet.Parameters.Count; k++) { - var isPrefixed = predictionSet.Parameters[k].Item1.StartsWith(parameter.Item1, StringComparison.OrdinalIgnoreCase); + var isPrefixed = predictionSet.Parameters[k].Name.StartsWith(parameter.Name, StringComparison.OrdinalIgnoreCase); var hasNotBeenUsed = !usedParams.Contains(k); if (isPrefixed && hasNotBeenUsed) { @@ -285,8 +292,8 @@ private static int FindParameterPositionInSet(Tuple parameter, P } /// - /// Escaping the prediction text is necessary because KnowledgeBase predicted suggestions - /// such as "<PSSubnetConfig>" are incorrectly identified as pipe operators + /// Escaping the prediction text is necessary because KnowledgeBase predicted suggestions. + /// such as "<PSSubnetConfig>" are incorrectly identified as pipe operators. /// /// The text to escape. private static string EscapePredictionText(string text) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLineSuggestion.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLineSuggestion.cs new file mode 100644 index 000000000000..37afb23745b2 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLineSuggestion.cs @@ -0,0 +1,102 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; +using System; +using System.Collections.Generic; +using System.Management.Automation.Subsystem; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor +{ + /// + /// Represents the suggestions to show to the user and the related information of the suggestions. + /// + /// + /// Because the performance requirement in , + /// it contains lists of each piece of information, for example, a collection of predictive suggestion and a list of + /// suggestion sources. Note that the count of each list should be the same. And each element in the list corresonds to + /// the element in other list at the same index. + /// + public sealed class CommandLineSuggestion + { + private readonly List _predictiveSuggestions = new(); + + /// + /// Gets the suggestions returned to show to the user. This can be adjusted from based on + /// the user input. + /// + public IReadOnlyList PredictiveSuggestions { get { CheckObjectInvariant(); return _predictiveSuggestions; } } + + private readonly List _sourceTexts = new(); + /// + /// Gets the texts that is based on. + /// + public IReadOnlyList SourceTexts { get { CheckObjectInvariant(); return _sourceTexts; } } + + private readonly List _suggestionSources = new(); + /// + /// Gets or sets the sources where the text is from. + /// + public IReadOnlyList SuggestionSources { get { CheckObjectInvariant(); return _suggestionSources; } } + + /// + /// Gets the number of suggestions. + /// + public int Count { get { CheckObjectInvariant(); return _suggestionSources.Count; } } + + /// + /// Adds a new suggestion. + /// + /// The suggestion to show to the user. + /// The text that used to construct . + public void AddSuggestion(PredictiveSuggestion predictiveSuggestion, string sourceText) => AddSuggestion(predictiveSuggestion, sourceText, SuggestionSource.None); + + /// + /// Adds a new suggestion. + /// + /// The suggestion to show to the user. + /// The text that used to construct . + /// The source where the suggestion is from. + public void AddSuggestion(PredictiveSuggestion predictiveSuggestion, string sourceText, SuggestionSource suggestionSource) + { + Validation.CheckArgument(predictiveSuggestion, $"{nameof(predictiveSuggestion)} cannot be null."); + Validation.CheckArgument(!string.IsNullOrWhiteSpace(predictiveSuggestion.SuggestionText), $"{nameof(predictiveSuggestion)} cannot have a null or whitespace suggestion text."); + Validation.CheckArgument(!string.IsNullOrWhiteSpace(sourceText), $"{nameof(sourceText)} cannot be null or whitespace."); + + _predictiveSuggestions.Add(predictiveSuggestion); + _sourceTexts.Add(sourceText); + _suggestionSources.Add(suggestionSource); + + CheckObjectInvariant(); + } + + /// + /// Updates the suggestion source of a suggestion. + /// + /// The index of a suggestion. + /// The new suggestion source. + public void UpdateSuggestionSource(int index, SuggestionSource suggestionSource) + { + Validation.CheckArgument((index >= 0) && (index < _suggestionSources.Count), $"{nameof(index)} is out of range."); + + _suggestionSources[index] = suggestionSource; + CheckObjectInvariant(); + } + + private void CheckObjectInvariant() + { + Validation.CheckInvariant(_predictiveSuggestions.Count == _sourceTexts.Count && _predictiveSuggestions.Count == _suggestionSources.Count); + } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/IAzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/IAzPredictorService.cs index 1a8c25c0e1c8..038935825aa0 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/IAzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/IAzPredictorService.cs @@ -12,7 +12,6 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; using System.Collections.Generic; using System.Management.Automation.Language; using System.Threading; @@ -27,23 +26,23 @@ public interface IAzPredictorService /// /// Gest the suggestions for the user input. /// - /// User input from PSReadLine + /// User input from PSReadLine. /// The number of suggestion to return. - /// The maximum amount of the same commnds in the list of predictions. /// The cancellation token - /// The list of suggestions for and the source that create the suggestion. The maximum number of suggestion is - public IEnumerable> GetSuggestion(Ast input, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken); + /// The maximum amount of the same commnds in the list of predictions. + /// The suggestions for . The maximum number of suggestions is . + public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken); /// /// Requests predictions, given a command string. /// - /// A list of commands + /// A list of commands. public void RequestPredictions(IEnumerable commands); /// /// Record the history from PSReadLine. /// - /// The last command in history + /// The last command in history. public void RecordHistory(CommandAst history); /// diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs index 53379b5393d2..947a22d76ca1 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs @@ -45,10 +45,10 @@ public interface ITelemetryClient public void OnRequestPrediction(string command); /// - /// Collects the event when we fail to get the prediction for the command + /// Collects the event when we fail to get the prediction for the command. /// /// The command to that we request the prediction for. - /// The exception + /// The exception. public void OnRequestPredictionError(string command, Exception e); /// @@ -60,15 +60,16 @@ public interface ITelemetryClient /// /// Collects when we return a suggestion /// - /// The user input that the suggestions are for - /// The list of suggestion and its source - /// Indicates whether the caller has cancelled the call to get suggestion. Usually that's because of time out - public void OnGetSuggestion(string maskedUserInput, IEnumerable> suggestions, bool isCancelled); + /// The user input that the suggestions are for. + /// The list of suggestions. + /// The list of sources for each of . + /// Indicates whether the caller has cancelled the call to get suggestion. Usually that's because of time out. + public void OnGetSuggestion(string maskedUserInput, IEnumerable suggestions, IEnumerable suggestionSources, bool isCancelled); /// /// Collects when an exception is thrown when we return a suggestion. /// - /// The exception + /// The exception. public void OnGetSuggestionError(Exception e); } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Parameter.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Parameter.cs new file mode 100644 index 000000000000..112d31c7f910 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Parameter.cs @@ -0,0 +1,48 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor +{ + /// + /// The class represents a name-value pair of a parameter. + /// + struct Parameter + { + /// + /// Gets the name of the parameter. + /// + public string Name { get; } + + /// + /// Gets or sets the valus of the parameter. + /// null if there is no valud is expected or set for this parameter. + /// + public string Value { get; set; } + + /// + /// Creates a new instance of + /// + /// The name of the parameter + /// The value of the parameter. If the parameter is a switch parameter, it's null. + public Parameter(string name, string value) + { + Validation.CheckArgument(!string.IsNullOrWhiteSpace(name), $"{nameof(name)} cannot be null or whitespace"); + + Name = name; + Value = value; + } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs index ad0de6d019e1..e24a0328e84e 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.Collections.Generic; using System.Linq; @@ -26,11 +27,16 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor /// sealed class ParameterSet { - public IList> Parameters { get; } + /// + /// Gets the list of the parameters with their names and values. + /// + public IReadOnlyList Parameters { get; } public ParameterSet(CommandAst commandAst) { - Parameters = new List>(); + Validation.CheckArgument(commandAst, $"{nameof(commandAst)} cannot be null."); + + var parameters = new List(); var elements = commandAst.CommandElements.Skip(1); Ast param = null; Ast arg = null; @@ -40,7 +46,7 @@ public ParameterSet(CommandAst commandAst) { if (param != null) { - Parameters.Add(new Tuple(param.ToString(), arg?.ToString())); + parameters.Add(new Parameter(param.ToString(), arg?.ToString())); } param = elem; arg = null; @@ -52,7 +58,7 @@ public ParameterSet(CommandAst commandAst) // We'll ignore the incomplete parameter. if (param != null) { - Parameters.Add(new Tuple(param.ToString(), arg?.ToString())); + parameters.Add(new Parameter(param.ToString(), arg?.ToString())); } param = null; @@ -64,15 +70,14 @@ public ParameterSet(CommandAst commandAst) } } + Validation.CheckInvariant((param != null) || (arg == null)); if (param != null) { - Parameters.Add(new Tuple(param.ToString(), arg?.ToString())); - } - else if (arg != null) - { - throw new InvalidOperationException(); + parameters.Add(new Parameter(param.ToString(), arg?.ToString())); } + + Parameters = parameters; } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs index 01effa836f43..4dfabbe10a9b 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs @@ -37,7 +37,7 @@ sealed class Settings public string ServiceUri { get; set; } /// - /// The number of suggestions to return to PSReadLine + /// The number of suggestions to return to PSReadLine. /// public int? SuggestionCount { get; set; } public int? MaxAllowedCommandDuplicate { get; set; } @@ -115,12 +115,12 @@ private void OverrideSettingsFromProfile() if (!string.IsNullOrWhiteSpace(profileSettings.ServiceUri)) { - this.ServiceUri = profileSettings.ServiceUri; + ServiceUri = profileSettings.ServiceUri; } if (profileSettings.SuggestionCount.HasValue && (profileSettings.SuggestionCount.Value > 0)) { - this.SuggestionCount = profileSettings.SuggestionCount; + SuggestionCount = profileSettings.SuggestionCount; } if (profileSettings.MaxAllowedCommandDuplicate.HasValue && (profileSettings.MaxAllowedCommandDuplicate.Value > 0)) @@ -141,7 +141,7 @@ private void OverrideSettingsFromEnv() if (!string.IsNullOrWhiteSpace(serviceUri)) { - this.ServiceUri = serviceUri; + ServiceUri = serviceUri; } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/PredictionSource.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/SuggestionSource.cs similarity index 78% rename from tools/Az.Tools.Predictor/Az.Tools.Predictor/PredictionSource.cs rename to tools/Az.Tools.Predictor/Az.Tools.Predictor/SuggestionSource.cs index 801e0796d18c..cdb890300a00 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/PredictionSource.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/SuggestionSource.cs @@ -18,10 +18,10 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { /// - /// An enum for the source where we get the prediction. + /// An enum for the source where we get the suggestion. /// [JsonConverter(typeof(StringEnumConverter))] - public enum PredictionSource + public enum SuggestionSource { /// /// There is no predictions. @@ -29,17 +29,17 @@ public enum PredictionSource None, /// - /// The prediction is from the static command list. + /// The suggestion is from the static command list. This doesn't take command history into account. /// StaticCommands, /// - /// The prediction is from the list for the older command. + /// The suggestion is from the list for outdated command history. /// PreviousCommand, /// - /// The prediction is from the list for the currentc command. + /// The suggestion is from the list for latest command history. /// CurrentCommand } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Validation.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Validation.cs new file mode 100644 index 000000000000..f90016554830 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/Validation.cs @@ -0,0 +1,77 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities +{ + internal static class Validation + { + public static void CheckArgument(bool argumentCondition, string message = null) + { + if (!argumentCondition) + { + throw new ArgumentException(message); + } + } + + public static void CheckArgument(T arg, string message = null) where T : class + { + if (arg == null) + { + throw new ArgumentNullException(message); + } + } + + public static void CheckArgument(bool argumentCondition, string message = null) where TException : Exception, new() + { + if (!argumentCondition) + { + Validation.Throw(message); + } + } + + public static void CheckInvariant(bool variantCondition, string message = null) + { + Validation.CheckInvariant(variantCondition, message); + } + + public static void CheckInvariant(bool variantCondition, string message = null) where TException : Exception, new() + { + if (!variantCondition) + { + Validation.Throw(message); + } + } + + private static void Throw(string message) where TException : Exception, new() + { + if (string.IsNullOrEmpty(message)) + { + throw new TException(); + } + else + { + Type exType = typeof(TException); + Exception exception = Activator.CreateInstance(exType, message) as Exception; + + if (exception != null) + { + throw exception; + } + } + } + } +} + diff --git a/tools/Az.Tools.Predictor/MockPSConsole/Microsoft.PowerShell.PSReadLine.Polyfiller.dll b/tools/Az.Tools.Predictor/MockPSConsole/Microsoft.PowerShell.PSReadLine.Polyfiller.dll index 8831829a56e1d74ee980cbdec6c709f8ed309ba0..b3d88d992a05819d33fa0f90d1909a5d57050481 100644 GIT binary patch literal 13704 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 q%;IcNIZCU-Hf=wrK`|r{i2nn!UAsd7 literal 313344 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%0A_YLTno^y+p)TkJR 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 /// Check whether the data collection is opted in from user. /// diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index 00efca93ec65..3979949cb13f 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation.Language; +using System.Management.Automation.Subsystem; using System.Text; using System.Threading; @@ -156,7 +157,7 @@ public Tuple> GetSuggestion(Ast result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); presentCommands.Add(_commandLinePredictions[i].Name, 1); } - else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDupl) + else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDuplicate) { result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); presentCommands[_commandLinePredictions[i].Name] += 1; diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs index 947a22d76ca1..a6a108440e59 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs @@ -42,14 +42,9 @@ public interface ITelemetryClient /// Collects the event when a prediction is requested. ///

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%0AHsHkm?hLc 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 Date: Wed, 18 Nov 2020 16:39:30 -0800 Subject: [PATCH 03/15] Fix a bug that throws NullReferenceException. --- .../Az.Tools.Predictor/AzPredictorService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index f04edde6d725..39f105bf7f44 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -185,8 +185,9 @@ public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int m if ((result == null) || (result.Count < suggestionCount)) { var fallbackPredictor = _fallbackPredictor; - var resultsFromFallbackTuple = fallbackPredictor?.GetSuggestion(input, presentCommands, suggestionCount - result.Count, maxAllowedCommandDuplicate, cancellationToken); - var resultsFromFallback = resultsFromCommandsTuple.Item1; + var suggestionCountToRequest = (result == null) ? suggestionCount : suggestionCount - result.Count; + var resultsFromFallbackTuple = fallbackPredictor?.GetSuggestion(input, presentCommands, suggestionCountToRequest, maxAllowedCommandDuplicate, cancellationToken); + var resultsFromFallback = resultsFromFallbackTuple.Item1; presentCommands = resultsFromFallbackTuple.Item2.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); if (result == null) From 2507f944a1bc4eb8cbf40591fbde1bce908b3814 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Mon, 30 Nov 2020 16:27:59 -0800 Subject: [PATCH 04/15] Improve the telemetry. - Combine the error telemetry event with the non-error one. - Collect if the http request is canceled when we send http request. --- .../Az.Tools.Predictor/AzPredictor.cs | 27 ++++-- .../Az.Tools.Predictor/AzPredictorService.cs | 87 ++++++++++--------- .../AzPredictorTelemetryClient.cs | 50 ++--------- .../CommandLinePredictor.cs | 3 +- .../Az.Tools.Predictor/ITelemetryClient.cs | 21 ++--- 5 files changed, 81 insertions(+), 107 deletions(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs index c8c6017fc7e1..87044dd07953 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs @@ -177,11 +177,16 @@ public List GetSuggestion(PredictionContext context, Cance { if (_settings.SuggestionCount.Value > 0) { + Exception exception = null; + string maskedUserInput = null; + CommandLineSuggestion suggestions = null; + try { var localCancellationToken = Settings.ContinueOnTimeout ? CancellationToken.None : cancellationToken; - var maskedUserInput = AzPredictor.MaskCommandLine(context.InputAst.FindAll((ast) => ast is CommandAst, true).LastOrDefault() as CommandAst); - var suggestions = _service.GetSuggestion(context.InputAst, _settings.SuggestionCount.Value, _settings.MaxAllowedCommandDuplicate.Value, localCancellationToken); + + maskedUserInput = AzPredictor.MaskCommandLine(context.InputAst.FindAll((ast) => ast is CommandAst, true).LastOrDefault() as CommandAst); + suggestions = _service.GetSuggestion(context.InputAst, _settings.SuggestionCount.Value, _settings.MaxAllowedCommandDuplicate.Value, localCancellationToken); localCancellationToken.ThrowIfCancellationRequested(); @@ -203,18 +208,22 @@ public List GetSuggestion(PredictionContext context, Cance localCancellationToken.ThrowIfCancellationRequested(); var returnedValue = suggestions.PredictiveSuggestions.ToList(); - - _telemetryClient.OnGetSuggestion(maskedUserInput, - suggestions.SourceTexts, - suggestions.SuggestionSources, - cancellationToken.IsCancellationRequested); - return returnedValue; } catch (Exception e) when (!(e is OperationCanceledException)) { - _telemetryClient.OnGetSuggestionError(e); + exception = e; + } + finally + { + + _telemetryClient.OnGetSuggestion(maskedUserInput, + suggestions?.SourceTexts, + suggestions?.SuggestionSources, + cancellationToken.IsCancellationRequested, + exception); + } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index 39f105bf7f44..366d736ef31e 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -159,7 +159,7 @@ public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int m var command = _commandToRequestPrediction; var presentCommands = new System.Collections.Generic.Dictionary(); - var resultsFromSuggestionTuple = commandBasedPredictor?.Item2?.GetSuggestion(input, presentCommands, suggestionCount, cancellationToken); + var resultsFromSuggestionTuple = commandBasedPredictor?.Item2?.GetSuggestion(input, presentCommands, suggestionCount, maxAllowedCommandDuplicate, cancellationToken); var result = resultsFromSuggestionTuple.Item1; presentCommands = resultsFromSuggestionTuple.Item2.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); @@ -217,56 +217,65 @@ public virtual void RequestPredictions(IEnumerable commands) Validation.CheckArgument(commands, $"{nameof(commands)} cannot be null."); var localCommands= string.Join(AzPredictorConstants.CommandConcatenator, commands); - _telemetryClient.OnRequestPrediction(localCommands); + bool postSuccess = false; + Exception exception = null; - if (string.Equals(localCommands, _commandToRequestPrediction, StringComparison.Ordinal)) + try { - // It's the same history we've already requested the prediction for last time, skip it. - return; - } - - if (commands.Any()) - { - SetCommandToRequestPrediction(localCommands); + if (string.Equals(localCommands, _commandToRequestPrediction, StringComparison.Ordinal)) + { + // It's the same history we've already requested the prediction for last time, skip it. + return; + } - // When it's called multiple times, we only need to keep the one for the latest command. + if (commands.Any()) + { + SetCommandToRequestPrediction(localCommands); - _predictionRequestCancellationSource?.Cancel(); - _predictionRequestCancellationSource = new CancellationTokenSource(); + // When it's called multiple times, we only need to keep the one for the latest command. - var cancellationToken = _predictionRequestCancellationSource.Token; + _predictionRequestCancellationSource?.Cancel(); + _predictionRequestCancellationSource = new CancellationTokenSource(); - // We don't need to block on the task. We send the HTTP request and update prediction list at the background. - Task.Run(async () => { - try - { - AzPredictorService.ReplaceThrottleUserIdToHeader(_client?.DefaultRequestHeaders, _azContext.UserId); + var cancellationToken = _predictionRequestCancellationSource.Token; - var requestContext = new PredictionRequestBody.RequestContext() + // We don't need to block on the task. We send the HTTP request and update prediction list at the background. + Task.Run(async () => { + try { - SessionId = _telemetryClient.SessionId, - CorrelationId = _telemetryClient.CorrelationId, - }; + AzPredictorService.ReplaceThrottleUserIdToHeader(_client?.DefaultRequestHeaders, _azContext.UserId); - var requestBody = new PredictionRequestBody(localCommands) - { - Context = requestContext, - }; + var requestContext = new PredictionRequestBody.RequestContext() + { + SessionId = _telemetryClient.SessionId, + CorrelationId = _telemetryClient.CorrelationId, + }; - var requestBodyString = JsonConvert.SerializeObject(requestBody); - var httpResponseMessage = await _client.PostAsync(_predictionsEndpoint, new StringContent(requestBodyString, Encoding.UTF8, "application/json"), cancellationToken); + var requestBody = new PredictionRequestBody(localCommands) + { + Context = requestContext, + }; - var reply = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); - var suggestionsList = JsonConvert.DeserializeObject>(reply); + var requestBodyString = JsonConvert.SerializeObject(requestBody); + var httpResponseMessage = await _client.PostAsync(_predictionsEndpoint, new StringContent(requestBodyString, Encoding.UTF8, "application/json"), cancellationToken); + postSuccess = true; - SetCommandBasedPreditor(localCommands, suggestionsList); - } - catch (Exception e) when (!(e is OperationCanceledException)) - { - _telemetryClient.OnRequestPredictionError(localCommands, e); - } - }, - cancellationToken); + var reply = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); + var suggestionsList = JsonConvert.DeserializeObject>(reply); + + SetCommandBasedPreditor(localCommands, suggestionsList); + } + catch (Exception e) when (!(e is OperationCanceledException)) + { + exception = e; + } + }, + cancellationToken); + } + } + finally + { + _telemetryClient.OnRequestPrediction(localCommands, postSuccess, exception); } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs index cdc725d0bed9..0d2612e4e17e 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs @@ -74,7 +74,7 @@ public void OnHistory(string historyLine) } /// - public void OnRequestPrediction(string command) + public void OnRequestPrediction(string command, bool isRequestSuccess, Exception exception) { if (!IsDataCollectionAllowed()) { @@ -84,7 +84,9 @@ public void OnRequestPrediction(string command) CorrelationId = Guid.NewGuid().ToString(); var properties = CreateProperties(); - properties.Add("Command", command); + properties.Add("Command", command ?? string.Empty); + properties.Add("IsRequestSuccess", isRequestSuccess.ToString(CultureInfo.InvariantCulture)); + properties.Add("exception", exception?.ToString() ?? string.Empty); _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/RequestPrediction", properties); @@ -93,25 +95,6 @@ public void OnRequestPrediction(string command) #endif } - /// - public void OnRequestPredictionError(string command, Exception e) - { - if (!IsDataCollectionAllowed()) - { - return; - } - - var properties = CreateProperties(); - properties.Add("Command", command); - properties.Add("Exception", e.ToString()); - - _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/RequestPredictionError", properties); - -#if TELEMETRY_TRACE && DEBUG - System.Diagnostics.Trace.WriteLine("Recording RequestPredictionError"); -#endif - } - /// public void OnSuggestionAccepted(string acceptedSuggestion) { @@ -131,7 +114,7 @@ public void OnSuggestionAccepted(string acceptedSuggestion) } /// - public void OnGetSuggestion(string maskedUserInput, IEnumerable suggestions, IEnumerable suggestionSource, bool isCancelled) + public void OnGetSuggestion(string maskedUserInput, IEnumerable suggestions, IEnumerable suggestionSource, bool isCancelled, Exception exception) { if (!IsDataCollectionAllowed()) { @@ -139,9 +122,10 @@ public void OnGetSuggestion(string maskedUserInput, IEnumerable suggesti } var properties = CreateProperties(); - properties.Add("UserInput", maskedUserInput); - properties.Add("Suggestion", JsonConvert.SerializeObject(suggestions.Zip(suggestionSource).Select((s) => ValueTuple.Create(s.First, s.Second)))); + properties.Add("UserInput", maskedUserInput ?? string.Empty); + properties.Add("Suggestion", suggestions != null ? JsonConvert.SerializeObject(suggestions.Zip(suggestionSource).Select((s) => ValueTuple.Create(s.First, s.Second))) : string.Empty); properties.Add("IsCancelled", isCancelled.ToString(CultureInfo.InvariantCulture)); + properties.Add("exception", exception?.ToString() ?? string.Empty); _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/GetSuggestion", properties); @@ -150,24 +134,6 @@ public void OnGetSuggestion(string maskedUserInput, IEnumerable suggesti #endif } - /// - public void OnGetSuggestionError(Exception e) - { - if (!IsDataCollectionAllowed()) - { - return; - } - - var properties = CreateProperties(); - properties.Add("Exception", e.ToString()); - - _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/GetSuggestionError", properties); - -#if TELEMETRY_TRACE && DEBUG - System.Diagnostics.Trace.WriteLine("Recording GetSuggestioinError"); -#endif - } - ///

/// The command to that we request the prediction for. - public void OnRequestPrediction(string command); - - /// - /// Collects the event when we fail to get the prediction for the command. - /// - /// The command to that we request the prediction for. - /// The exception. - public void OnRequestPredictionError(string command, Exception e); + /// Indicates whether the http request is send. + /// The exception if there is an error. + public void OnRequestPrediction(string command, bool isRequestSuccess, Exception exception); /// /// Collects when a suggestion is accepted. @@ -64,13 +59,7 @@ public interface ITelemetryClient /// The list of suggestions. /// The list of sources for each of . /// Indicates whether the caller has cancelled the call to get suggestion. Usually that's because of time out. - public void OnGetSuggestion(string maskedUserInput, IEnumerable suggestions, IEnumerable suggestionSources, bool isCancelled); - - /// - /// Collects when an exception is thrown when we return a suggestion. - /// - /// The exception. - - public void OnGetSuggestionError(Exception e); + /// The exception if there is an error. + public void OnGetSuggestion(string maskedUserInput, IEnumerable suggestions, IEnumerable suggestionSources, bool isCancelled, Exception exception); } } From 2d88593911c66f71ab72a4dd9fb741186644e4d5 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Mon, 30 Nov 2020 16:39:37 -0800 Subject: [PATCH 05/15] Fix a bug that out of range is thrown. --- .../Az.Tools.Predictor/CommandLinePredictor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index 3979949cb13f..958be37594fb 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -178,9 +178,13 @@ public Tuple> GetSuggestion(Ast catch { } + if ((result.Count < suggestionCount) && (resultsTemp.Count > 0)) { - resultsTemp.ToList().GetRange(0, suggestionCount - result.Count).ForEach(x => result.AddSuggestion(new PredictiveSuggestion(x.Key), x.Value)); + foreach (var temp in resultsTemp.Take(suggestionCount - result.Count)) + { + result.AddSuggestion(new PredictiveSuggestion(temp.Key), temp.Value); + } } return new Tuple>(result, presentCommands); From df2db75094a111eb3e855568a033d8e28fc515e3 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Wed, 2 Dec 2020 15:52:01 -0800 Subject: [PATCH 06/15] Transform and send telemetry in a thread pool. - Refactor the telemetry and use a class for the collected data in each telemetry event. - Now we only get basic information and push them to the data flow. - A thread from thread pool handles the data, transform them, and send it. --- .../Az.Tools.Predictor/AzPredictor.cs | 145 +++----------- .../AzPredictorConstants.cs | 10 + .../Az.Tools.Predictor/AzPredictorService.cs | 39 ++-- .../AzPredictorTelemetryClient.cs | 160 +++++++++++++--- .../CommandLinePredictor.cs | 136 ++++++------- .../Az.Tools.Predictor/ITelemetryClient.cs | 25 +-- .../Az.Tools.Predictor/TelemetryData.cs | 180 ++++++++++++++++++ .../Utilities/CommandLineUtilities.cs | 78 ++++++++ 8 files changed, 517 insertions(+), 256 deletions(-) create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs index 87044dd07953..5b84e7a61b87 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utitlities; using System; using System.Collections.Generic; using System.Linq; @@ -19,7 +20,6 @@ using System.Management.Automation.Language; using System.Management.Automation.Subsystem; using System.Runtime.CompilerServices; -using System.Text; using System.Threading; [assembly:InternalsVisibleTo("Microsoft.Azure.PowerShell.Tools.AzPredictor.Test")] @@ -49,8 +49,6 @@ internal sealed class AzPredictor : ICommandPredictor internal static readonly Guid Identifier = new Guid("599d1760-4ee1-4ed2-806e-f2a1b1a0ba4d"); private const int SuggestionCountForTelemetry = 5; - private const string ParameterValueMask = "***"; - private const char ParameterValueSeperator = ':'; private static readonly string[] CommonParameters = new string[] { "location" }; @@ -61,11 +59,6 @@ internal sealed class AzPredictor : ICommandPredictor private Queue _lastTwoMaskedCommands = new Queue(AzPredictorConstants.CommandHistoryCountToProcess); - /// - /// the adjusted texts and the source text for the suggestion. - /// - private Dictionary _userAcceptedAndSuggestion = new Dictionary(); - /// /// Constructs a new instance of . /// @@ -86,10 +79,6 @@ public void StartEarlyProcessing(IReadOnlyList history) { // The context only changes when the user executes the corresponding command. _azContext?.UpdateContext(); - lock (_userAcceptedAndSuggestion) - { - _userAcceptedAndSuggestion.Clear(); - } if (history.Count > 0) { @@ -127,7 +116,7 @@ public void StartEarlyProcessing(IReadOnlyList history) _service.RecordHistory(lastCommand.Item1); } - _telemetryClient.OnHistory(lastCommand.Item2); + _telemetryClient.OnHistory(new TelemetryData.History(lastCommand.Item2)); _service.RequestPredictions(_lastTwoMaskedCommands); } @@ -142,7 +131,7 @@ ValueTuple GetAstAndMaskedCommandLine(string commandLine) if (_service.IsSupportedCommand(commandName)) { - maskedCommandLine = AzPredictor.MaskCommandLine(commandAst); + maskedCommandLine = CommandLineUtilities.MaskCommandLine(commandAst); } else { @@ -156,130 +145,42 @@ ValueTuple GetAstAndMaskedCommandLine(string commandLine) /// public void OnSuggestionAccepted(string acceptedSuggestion) { - IDictionary localSuggestedTexts = null; - lock (_userAcceptedAndSuggestion) - { - localSuggestedTexts = _userAcceptedAndSuggestion; - } - - if (localSuggestedTexts.TryGetValue(acceptedSuggestion, out var suggestedText)) - { - _telemetryClient.OnSuggestionAccepted(suggestedText); - } - else - { - _telemetryClient.OnSuggestionAccepted("NoRecord"); - } + _telemetryClient.OnSuggestionAccepted(new TelemetryData.SuggestionAccepted(acceptedSuggestion)); } /// public List GetSuggestion(PredictionContext context, CancellationToken cancellationToken) { - if (_settings.SuggestionCount.Value > 0) + if (_settings.SuggestionCount.Value <= 0) { - Exception exception = null; - string maskedUserInput = null; - CommandLineSuggestion suggestions = null; - - try - { - var localCancellationToken = Settings.ContinueOnTimeout ? CancellationToken.None : cancellationToken; - - maskedUserInput = AzPredictor.MaskCommandLine(context.InputAst.FindAll((ast) => ast is CommandAst, true).LastOrDefault() as CommandAst); - suggestions = _service.GetSuggestion(context.InputAst, _settings.SuggestionCount.Value, _settings.MaxAllowedCommandDuplicate.Value, localCancellationToken); - - localCancellationToken.ThrowIfCancellationRequested(); - - var userAcceptedAndSuggestion = new Dictionary(); - - for (int i = 0; i < suggestions.Count; ++i) - { - userAcceptedAndSuggestion[suggestions.PredictiveSuggestions[i].SuggestionText] = suggestions.SourceTexts[i]; - } - - lock (_userAcceptedAndSuggestion) - { - foreach (var u in userAcceptedAndSuggestion) - { - _userAcceptedAndSuggestion[u.Key] = u.Value; - } - } - - localCancellationToken.ThrowIfCancellationRequested(); + return new List(); + } - var returnedValue = suggestions.PredictiveSuggestions.ToList(); - return returnedValue; + Exception exception = null; + CommandLineSuggestion suggestions = null; - } - catch (Exception e) when (!(e is OperationCanceledException)) - { - exception = e; - } - finally - { + try + { + var localCancellationToken = Settings.ContinueOnTimeout ? CancellationToken.None : cancellationToken; - _telemetryClient.OnGetSuggestion(maskedUserInput, - suggestions?.SourceTexts, - suggestions?.SuggestionSources, - cancellationToken.IsCancellationRequested, - exception); + suggestions = _service.GetSuggestion(context.InputAst, _settings.SuggestionCount.Value, _settings.MaxAllowedCommandDuplicate.Value, localCancellationToken); - } + var returnedValue = suggestions?.PredictiveSuggestions?.ToList(); + return returnedValue ?? new List(); } - - return new List(); - } - - /// - /// Masks the user input of any data, like names and locations. - /// Also alphabetizes the parameters to normalize them before sending - /// them to the model. - /// e.g., Get-AzContext -Name Hello -Location 'EastUS' => Get-AzContext -Location *** -Name *** - /// - /// The last user input command. - private static string MaskCommandLine(CommandAst cmdAst) - { - var commandElements = cmdAst?.CommandElements; - - if (commandElements == null) + catch (Exception e) when (!(e is OperationCanceledException)) { - return null; + exception = e; + return new List(); } - - if (commandElements.Count == 1) + finally { - return cmdAst.Extent.Text; - } - - var sb = new StringBuilder(cmdAst.Extent.Text.Length); - _ = sb.Append(commandElements[0].ToString()); - var parameters = commandElements - .Skip(1) - .Where(element => element is CommandParameterAst) - .Cast() - .OrderBy(ast => ast.ParameterName); - foreach (CommandParameterAst param in parameters) - { - _ = sb.Append(AzPredictorConstants.CommandParameterSeperator); - if (param.Argument != null) - { - // Parameter is in the form of `-Name:name` - _ = sb.Append(AzPredictorConstants.ParameterIndicator) - .Append(param.ParameterName) - .Append(AzPredictor.ParameterValueSeperator) - .Append(AzPredictor.ParameterValueMask); - } - else - { - // Parameter is in the form of `-Name` - _ = sb.Append(AzPredictorConstants.ParameterIndicator) - .Append(param.ParameterName) - .Append(AzPredictorConstants.CommandParameterSeperator) - .Append(AzPredictor.ParameterValueMask); - } + _telemetryClient.OnGetSuggestion(new TelemetryData.GetSuggestion(context.InputAst, + suggestions, + cancellationToken.IsCancellationRequested, + exception)); } - return sb.ToString(); } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorConstants.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorConstants.cs index 98582e59a5b4..84537e52ea46 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorConstants.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorConstants.cs @@ -59,6 +59,16 @@ internal static class AzPredictorConstants /// public const char ParameterIndicator = '-'; + /// + /// The seperator used in parameter name and value pair which is in the form -Name:Value. + /// + public const char ParameterValueSeperator = ':'; + + /// + /// The substitute for the parameter value. + /// + public const string ParameterValueMask = "***"; + /// /// The setting file name. /// diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index 366d736ef31e..e82b9502bc85 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -149,32 +149,22 @@ public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int m { Validation.CheckArgument(input, $"{nameof(input)} cannot be null"); Validation.CheckArgument(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0."); - - if (suggestionCount == 0) - { - return null; - } + Validation.CheckArgument(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0."); var commandBasedPredictor = _commandBasedPredictor; var command = _commandToRequestPrediction; - var presentCommands = new System.Collections.Generic.Dictionary(); - var resultsFromSuggestionTuple = commandBasedPredictor?.Item2?.GetSuggestion(input, presentCommands, suggestionCount, maxAllowedCommandDuplicate, cancellationToken); - var result = resultsFromSuggestionTuple.Item1; - presentCommands = resultsFromSuggestionTuple.Item2.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + var presentCommands = new Dictionary(); + var result = commandBasedPredictor?.Item2?.GetSuggestion(input, presentCommands, suggestionCount, maxAllowedCommandDuplicate, cancellationToken); if ((result != null) && (result.Count > 0)) { - var suggestionSource = SuggestionSource.None; + var suggestionSource = SuggestionSource.PreviousCommand; if (string.Equals(command, commandBasedPredictor?.Item1, StringComparison.Ordinal)) { suggestionSource = SuggestionSource.CurrentCommand; } - else - { - suggestionSource = SuggestionSource.PreviousCommand; - } for (var i = 0; i < result.Count; ++i) { @@ -186,9 +176,7 @@ public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int m { var fallbackPredictor = _fallbackPredictor; var suggestionCountToRequest = (result == null) ? suggestionCount : suggestionCount - result.Count; - var resultsFromFallbackTuple = fallbackPredictor?.GetSuggestion(input, presentCommands, suggestionCountToRequest, maxAllowedCommandDuplicate, cancellationToken); - var resultsFromFallback = resultsFromFallbackTuple.Item1; - presentCommands = resultsFromFallbackTuple.Item2.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + var resultsFromFallback = fallbackPredictor?.GetSuggestion(input, presentCommands, suggestionCountToRequest, maxAllowedCommandDuplicate, cancellationToken); if (result == null) { @@ -203,7 +191,7 @@ public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int m continue; } - result.AddSuggestion(resultsFromFallback.PredictiveSuggestions[i], resultsFromFallback.SourceTexts[i], resultsFromFallback.SuggestionSources[i]); + result.AddSuggestion(resultsFromFallback.PredictiveSuggestions[i], resultsFromFallback.SourceTexts[i], SuggestionSource.StaticCommands); } } } @@ -219,6 +207,7 @@ public virtual void RequestPredictions(IEnumerable commands) var localCommands= string.Join(AzPredictorConstants.CommandConcatenator, commands); bool postSuccess = false; Exception exception = null; + bool startRequestTask = false; try { @@ -240,6 +229,7 @@ public virtual void RequestPredictions(IEnumerable commands) var cancellationToken = _predictionRequestCancellationSource.Token; // We don't need to block on the task. We send the HTTP request and update prediction list at the background. + startRequestTask = true; Task.Run(async () => { try { @@ -269,13 +259,24 @@ public virtual void RequestPredictions(IEnumerable commands) { exception = e; } + finally + { + _telemetryClient.OnRequestPrediction(new TelemetryData.RequestPrediction(localCommands, postSuccess, exception)); + } }, cancellationToken); } } + catch (Exception e) + { + exception = e; + } finally { - _telemetryClient.OnRequestPrediction(localCommands, postSuccess, exception); + if (!startRequestTask) + { + _telemetryClient.OnRequestPrediction(new TelemetryData.RequestPrediction(localCommands, hasSentHttpRequest: false, exception: exception)); + } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs index 0d2612e4e17e..b3ce7c7c568b 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs @@ -15,11 +15,14 @@ using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.Azure.PowerShell.Tools.AzPredictor.Profile; +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utitlities; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Management.Automation.Language; +using System.Threading.Tasks.Dataflow; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { @@ -36,9 +39,26 @@ sealed class AzPredictorTelemetryClient : ITelemetryClient /// public string CorrelationId { get; private set; } = Guid.NewGuid().ToString(); + /// + /// The client that sends the telemetry to the server. + /// private readonly TelemetryClient _telemetryClient; + private readonly IAzContext _azContext; - private Tuple, string> _cachedAzModulesVersions = Tuple.Create, string>(null, null); + + /// + /// The action to handle the in a thread pool. + /// + private readonly ActionBlock _telemetryDispatcher; + + /// + /// The adjusted texts and the source text for the suggestion. + /// + /// + /// We only access it in the thread pool which is to handle the data at the target side of the data flow. + /// We only handle one item at a time so there is no race condition. + /// + private IDictionary _userAcceptedAndSuggestion = new Dictionary(); /// /// Constructs a new instance of . @@ -53,20 +73,22 @@ public AzPredictorTelemetryClient(IAzContext azContext) _telemetryClient.Context.Cloud.RoleInstance = "placeholderdon'tuse"; _telemetryClient.Context.Cloud.RoleName = "placeholderdon'tuse"; _azContext = azContext; + _telemetryDispatcher = new ActionBlock( + (telemetryData) => DispatchTelemetryData(telemetryData)); } /// - public void OnHistory(string historyLine) + public void OnHistory(TelemetryData.History telemetryData) { if (!IsDataCollectionAllowed()) { return; } - var properties = CreateProperties(); - properties.Add("History", historyLine); + telemetryData.SessionId = SessionId; + telemetryData.CorrelationId = CorrelationId; - _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/CommandHistory", properties); + _telemetryDispatcher.Post(telemetryData); #if TELEMETRY_TRACE && DEBUG System.Diagnostics.Trace.WriteLine("Recording CommandHistory"); @@ -74,7 +96,7 @@ public void OnHistory(string historyLine) } /// - public void OnRequestPrediction(string command, bool isRequestSuccess, Exception exception) + public void OnRequestPrediction(TelemetryData.RequestPrediction telemetryData) { if (!IsDataCollectionAllowed()) { @@ -83,12 +105,10 @@ public void OnRequestPrediction(string command, bool isRequestSuccess, Exception CorrelationId = Guid.NewGuid().ToString(); - var properties = CreateProperties(); - properties.Add("Command", command ?? string.Empty); - properties.Add("IsRequestSuccess", isRequestSuccess.ToString(CultureInfo.InvariantCulture)); - properties.Add("exception", exception?.ToString() ?? string.Empty); + telemetryData.SessionId = SessionId; + telemetryData.CorrelationId = CorrelationId; - _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/RequestPrediction", properties); + _telemetryDispatcher.Post(telemetryData); #if TELEMETRY_TRACE && DEBUG System.Diagnostics.Trace.WriteLine("Recording RequestPrediction"); @@ -96,17 +116,17 @@ public void OnRequestPrediction(string command, bool isRequestSuccess, Exception } /// - public void OnSuggestionAccepted(string acceptedSuggestion) + public void OnSuggestionAccepted(TelemetryData.SuggestionAccepted telemetryData) { if (!IsDataCollectionAllowed()) { return; } - var properties = CreateProperties(); - properties.Add("AcceptedSuggestion", acceptedSuggestion); + telemetryData.SessionId = SessionId; + telemetryData.CorrelationId = CorrelationId; - _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/AcceptSuggestion", properties); + _telemetryDispatcher.Post(telemetryData); #if TELEMETRY_TRACE && DEBUG System.Diagnostics.Trace.WriteLine("Recording AcceptSuggestion"); @@ -114,20 +134,17 @@ public void OnSuggestionAccepted(string acceptedSuggestion) } /// - public void OnGetSuggestion(string maskedUserInput, IEnumerable suggestions, IEnumerable suggestionSource, bool isCancelled, Exception exception) + public void OnGetSuggestion(TelemetryData.GetSuggestion telemetryData) { if (!IsDataCollectionAllowed()) { return; } - var properties = CreateProperties(); - properties.Add("UserInput", maskedUserInput ?? string.Empty); - properties.Add("Suggestion", suggestions != null ? JsonConvert.SerializeObject(suggestions.Zip(suggestionSource).Select((s) => ValueTuple.Create(s.First, s.Second))) : string.Empty); - properties.Add("IsCancelled", isCancelled.ToString(CultureInfo.InvariantCulture)); - properties.Add("exception", exception?.ToString() ?? string.Empty); + telemetryData.SessionId = SessionId; + telemetryData.CorrelationId = CorrelationId; - _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/GetSuggestion", properties); + _telemetryDispatcher.Post(telemetryData); #if TELEMETRY_TRACE && DEBUG System.Diagnostics.Trace.WriteLine("Recording GetSuggestion"); @@ -148,15 +165,108 @@ private static bool IsDataCollectionAllowed() return false; } + /// + /// Dispatches according to its implementation. + /// + private void DispatchTelemetryData(TelemetryData.ITelemetryData telemetryData) + { + switch (telemetryData) + { + case TelemetryData.History history: + SendTelemetry(history); + break; + case TelemetryData.RequestPrediction requestPrediction: + SendTelemetry(requestPrediction); + break; + case TelemetryData.GetSuggestion getSuggestion: + SendTelemetry(getSuggestion); + break; + case TelemetryData.SuggestionAccepted suggestionAccepted: + SendTelemetry(suggestionAccepted); + break; + default: + throw new NotImplementedException(); + } + } + + /// + /// Sends the telemetry with the command history. + /// + private void SendTelemetry(TelemetryData.History telemetryData) + { + var properties = CreateProperties(telemetryData); + properties.Add("History", telemetryData.Command); + + _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/CommandHistory", properties); + } + + /// + /// Sends the telemetry with the commands for prediction. + /// + private void SendTelemetry(TelemetryData.RequestPrediction telemetryData) + { + _userAcceptedAndSuggestion.Clear(); + + var properties = CreateProperties(telemetryData); + properties.Add("Command", telemetryData.Commands ?? string.Empty); + properties.Add("HttpRequestSent", telemetryData.HasSentHttpRequest.ToString(CultureInfo.InvariantCulture)); + properties.Add("Exception", telemetryData.Exception?.ToString() ?? string.Empty); + + _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/RequestPrediction", properties); + } + + /// + /// Sends the telemetry with the suggestion returned to the user. + /// + private void SendTelemetry(TelemetryData.GetSuggestion telemetryData) + { + var suggestions = telemetryData.Suggestion?.PredictiveSuggestions; + var suggestionSource = telemetryData.Suggestion?.SuggestionSources; + var sourceTexts = telemetryData.Suggestion?.SourceTexts; + var maskedUserInput = CommandLineUtilities.MaskCommandLine(telemetryData.UserInput?.FindAll((ast) => ast is CommandAst, true).LastOrDefault() as CommandAst); + + if ((suggestions != null) && (sourceTexts != null)) + { + for (int i = 0; i < suggestions.Count; ++i) + { + _userAcceptedAndSuggestion[suggestions[i].SuggestionText] = sourceTexts[i]; + } + } + + var properties = CreateProperties(telemetryData); + properties.Add("UserInput", maskedUserInput ?? string.Empty); + properties.Add("Suggestion", sourceTexts != null ? JsonConvert.SerializeObject(sourceTexts.Zip(suggestionSource).Select((s) => ValueTuple.Create(s.First, s.Second))) : string.Empty); + properties.Add("IsCancelled", telemetryData.IsCancellationRequested.ToString(CultureInfo.InvariantCulture)); + properties.Add("Exception", telemetryData.Exception?.ToString() ?? string.Empty); + + _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/GetSuggestion", properties); + } + + /// + /// Sends the telemetry with the suggestion returned to the user. + /// + private void SendTelemetry(TelemetryData.SuggestionAccepted telemetryData) + { + if (!_userAcceptedAndSuggestion.TryGetValue(telemetryData.Suggestion, out var suggestion)) + { + suggestion = "NoRecord"; + } + + var properties = CreateProperties(telemetryData); + properties.Add("AcceptedSuggestion", suggestion); + + _telemetryClient.TrackEvent($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/AcceptSuggestion", properties); + } + /// /// Add the common properties to the telemetry event. /// - private IDictionary CreateProperties() + private IDictionary CreateProperties(TelemetryData.ITelemetryData telemetryData) { return new Dictionary() { - { "SessionId", SessionId }, - { "CorrelationId", CorrelationId }, + { "SessionId", telemetryData.SessionId }, + { "CorrelationId", telemetryData.CorrelationId }, { "UserId", _azContext.UserId }, { "HashMacAddress", _azContext.MacAddress }, { "PowerShellVersion", _azContext.PowerShellVersion.ToString() }, diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index 958be37594fb..e71df5ccb126 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -66,20 +66,17 @@ public CommandLinePredictor(IList modelPredictions, ParameterValuePredic /// Returns suggestions given the user input. /// /// PowerShell AST input of the user, generated by PSReadLine - /// Commands already present. + /// Commands already present. Contents may be added to this collection. /// The number of suggestions to return. /// The maximum amount of the same commnds in the list of predictions. /// The cancellation token - /// The collections of suggestions and related information. - public Tuple> GetSuggestion(Ast input, IDictionary presentCommands, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken) + /// The collections of suggestions. + public CommandLineSuggestion GetSuggestion(Ast input, IDictionary presentCommands, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken) { - Validation.CheckArgument(input, $"{nameof(input)} cannot but null."); + Validation.CheckArgument(input, $"{nameof(input)} cannot be null."); + Validation.CheckArgument(presentCommands, $"{nameof(presentCommands)} cannot be null."); Validation.CheckArgument(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0."); - - if (suggestionCount == 0) - { - return null; - } + Validation.CheckArgument(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0."); var commandAst = input.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; @@ -92,92 +89,85 @@ public Tuple> GetSuggestion(Ast CommandLineSuggestion result = new(); var resultsTemp = new Dictionary(StringComparer.OrdinalIgnoreCase); - try + var inputParameterSet = new ParameterSet(commandAst); + var isCommandNameComplete = (((commandAst?.CommandElements != null) && (commandAst.CommandElements.Count > 1)) || ((input as ScriptBlockAst)?.Extent?.Text?.EndsWith(' ') == true)); + + Func commandNameQuery = (command) => command.Equals(commandName, StringComparison.OrdinalIgnoreCase); + if (!isCommandNameComplete) { - var inputParameterSet = new ParameterSet(commandAst); + commandNameQuery = (command) => command.StartsWith(commandName, StringComparison.OrdinalIgnoreCase); + } - var isCommandNameComplete = (((commandAst?.CommandElements != null) && (commandAst.CommandElements.Count > 1)) || ((input as ScriptBlockAst)?.Extent?.Text?.EndsWith(' ') == true)); + // Try to find the matching command and arrange the parameters in the order of the input. + // + // Predictions should be flexible, e.g. if "Command -Name N -Location L" is a possibility, + // then "Command -Location L -Name N" should also be possible. + // + // resultBuilder and usedParams are used to store the information to construct the result. + // We want to avoid too much heap allocation for the performance purpose. - Func commandNameQuery = (command) => command.Equals(commandName, StringComparison.OrdinalIgnoreCase); - if (!isCommandNameComplete) - { - commandNameQuery = (command) => command.StartsWith(commandName, StringComparison.OrdinalIgnoreCase); - } + var resultBuilder = new StringBuilder(); + var usedParams = new HashSet(); + var sourceBuilder = new StringBuilder(); - // Try to find the matching command and arrange the parameters in the order of the input. - // - // Predictions should be flexible, e.g. if "Command -Name N -Location L" is a possibility, - // then "Command -Location L -Name N" should also be possible. - // - // resultBuilder and usedParams are used to store the information to construct the result. - // We want to avoid too much heap allocation for the performance purpose. + for (var i = 0; i < _commandLinePredictions.Count && result.Count < suggestionCount; ++i) + { + if (commandNameQuery(_commandLinePredictions[i].Name)) + { + cancellationToken.ThrowIfCancellationRequested(); - var resultBuilder = new StringBuilder(); - var usedParams = new HashSet(); - var sourceBuilder = new StringBuilder(); + resultBuilder.Clear(); + resultBuilder.Append(_commandLinePredictions[i].Name); + usedParams.Clear(); - for (var i = 0; i < _commandLinePredictions.Count && result.Count < suggestionCount; ++i) - { - if (commandNameQuery(_commandLinePredictions[i].Name)) + if (DoesPredictionParameterSetMatchInput(resultBuilder, inputParameterSet, _commandLinePredictions[i].ParameterSet, usedParams)) { - cancellationToken.ThrowIfCancellationRequested(); - - resultBuilder.Clear(); - resultBuilder.Append(_commandLinePredictions[i].Name); - usedParams.Clear(); + PredictRestOfParameters(resultBuilder, _commandLinePredictions[i].ParameterSet.Parameters, usedParams); + var prediction = UnescapePredictionText(resultBuilder); - if (DoesPredictionParameterSetMatchInput(resultBuilder, inputParameterSet, _commandLinePredictions[i].ParameterSet, usedParams)) + if (prediction.Length <= input.Extent.Text.Length) { - PredictRestOfParameters(resultBuilder, _commandLinePredictions[i].ParameterSet.Parameters, usedParams); - var prediction = UnescapePredictionText(resultBuilder); + continue; + } - if (prediction.Length <= input.Extent.Text.Length) - { - continue; - } + sourceBuilder.Clear(); + sourceBuilder.Append(_commandLinePredictions[i].Name); - sourceBuilder.Clear(); - sourceBuilder.Append(_commandLinePredictions[i].Name); + foreach (var p in _commandLinePredictions[i].ParameterSet.Parameters) + { + _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); + _ = sourceBuilder.Append(p.Name); - foreach (var p in _commandLinePredictions[i].ParameterSet.Parameters) + if (!string.IsNullOrWhiteSpace(p.Value)) { _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); _ = sourceBuilder.Append(p.Name); - - if (!string.IsNullOrWhiteSpace(p.Value)) - { - _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = sourceBuilder.Append(p.Name); - } } + } - if (!presentCommands.ContainsKey(_commandLinePredictions[i].Name)) - { - result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); - presentCommands.Add(_commandLinePredictions[i].Name, 1); - } - else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDuplicate) - { - result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); - presentCommands[_commandLinePredictions[i].Name] += 1; - } - else - { - resultsTemp.Add(prediction.ToString(), sourceBuilder.ToString()); - } + if (!presentCommands.ContainsKey(_commandLinePredictions[i].Name)) + { + result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); + presentCommands.Add(_commandLinePredictions[i].Name, 1); + } + else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDuplicate) + { + result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); + presentCommands[_commandLinePredictions[i].Name] += 1; + } + else + { + resultsTemp.Add(prediction.ToString(), sourceBuilder.ToString()); + } - if (result.Count == suggestionCount) - { - break; - } + if (result.Count == suggestionCount) + { + break; } } } } - catch - { - } if ((result.Count < suggestionCount) && (resultsTemp.Count > 0)) { @@ -187,7 +177,7 @@ public Tuple> GetSuggestion(Ast } } - return new Tuple>(result, presentCommands); + return result; } /// diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs index a6a108440e59..70356928e2b9 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs @@ -12,9 +12,6 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; -using System.Collections.Generic; - namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { /// @@ -35,31 +32,25 @@ public interface ITelemetryClient /// /// Collects the event of the history command. /// - /// The history command from PSReadLine. - public void OnHistory(string historyLine); + /// The data to collect. + public void OnHistory(TelemetryData.History telemetryData); /// /// Collects the event when a prediction is requested. /// - /// The command to that we request the prediction for. - /// Indicates whether the http request is send. - /// The exception if there is an error. - public void OnRequestPrediction(string command, bool isRequestSuccess, Exception exception); + /// The data to collect. + public void OnRequestPrediction(TelemetryData.RequestPrediction telemetryData); /// /// Collects when a suggestion is accepted. /// - /// The suggestion that's accepted by the user. - public void OnSuggestionAccepted(string acceptedSuggestion); + /// The data to collect. + public void OnSuggestionAccepted(TelemetryData.SuggestionAccepted telemetryData); /// /// Collects when we return a suggestion /// - /// The user input that the suggestions are for. - /// The list of suggestions. - /// The list of sources for each of . - /// Indicates whether the caller has cancelled the call to get suggestion. Usually that's because of time out. - /// The exception if there is an error. - public void OnGetSuggestion(string maskedUserInput, IEnumerable suggestions, IEnumerable suggestionSources, bool isCancelled, Exception exception); + /// The data to collect. + public void OnGetSuggestion(TelemetryData.GetSuggestion telemetryData); } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs new file mode 100644 index 000000000000..71fed343d169 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs @@ -0,0 +1,180 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Management.Automation.Language; +using System.Threading; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor +{ + /// + /// The collection of the data that're collected in telemetry. + /// + public static class TelemetryData + { + /// + /// An interface that all telemetry data class should implement. + /// + public interface ITelemetryData + { + /// + /// Gets the session id. + /// + string SessionId { get; } + + /// + /// Gets the correlation id. + /// + string CorrelationId { get; } + } + + /// + /// The data to collect in . + /// + public sealed class History : ITelemetryData + { + /// + public string SessionId { get; internal set; } + + /// + public string CorrelationId { get; internal set; } + + /// + /// Gets the history command line. + /// + public string Command { get; } + + /// + /// Creates a new instance of . + /// + /// The history command line. + public History(string command) => Command = command; + } + + /// + /// The data to collect in . + /// + public sealed class RequestPrediction : ITelemetryData + { + /// + public string SessionId { get; internal set; } + + /// + public string CorrelationId { get; internal set; } + + /// + /// Gets the masked command lines that are used to request prediction. + /// + public string Commands { get; } + + /// + /// Gets whether the http request to the service is sent. + /// + public bool HasSentHttpRequest { get; } + + /// + /// Gets the exception if there is an error during the operation. + /// + /// + /// OperationCanceledException isn't considered an error. + /// + public Exception Exception { get; } + + /// + /// Creates an instance of . + /// + /// The commands to request prediction for. + /// The flag to indicate whether the http request is canceled. + /// The exception that may be thrown. + public RequestPrediction(string commands, bool hasSentHttpRequest, Exception exception) + { + Commands = commands; + HasSentHttpRequest = hasSentHttpRequest; + Exception = exception; + } + } + + /// + /// The data to collect in . + /// + public sealed class GetSuggestion : ITelemetryData + { + /// + public string SessionId { get; internal set; } + + /// + public string CorrelationId { get; internal set; } + + /// + /// Gets the user input. + /// + public Ast UserInput { get; } + + /// + /// Gets the suggestions to return to the user. + /// + public CommandLineSuggestion Suggestion { get; } + + /// + /// Gets whether the cancellation request is already set. + /// + public bool IsCancellationRequested { get; } + + /// + /// Gets the exception if there is an error during the operation. + /// + /// + /// OperationCanceledException isn't considered an error. + /// + public Exception Exception { get; } + + /// + /// Creates a new instance of . + /// + /// The user input that the is for. + /// The suggestions returned for the . + /// Indicates if the cancellation has been requested. + /// The exception that is thrown if there is an error. + public GetSuggestion(Ast userInput, CommandLineSuggestion suggestion, bool isCancellationRequested, Exception exception) + { + UserInput = userInput; + Suggestion = suggestion; + IsCancellationRequested = isCancellationRequested; + Exception = exception; + } + } + + /// + /// The data to collect in . + /// + public sealed class SuggestionAccepted : ITelemetryData + { + /// + public string SessionId { get; internal set; } + + /// + public string CorrelationId { get; internal set; } + + /// + /// Gets the suggestion that's accepted by the user. + /// + public string Suggestion { get; } + + /// + /// Creates a new instance of . + /// + public SuggestionAccepted(string suggestion) => Suggestion = suggestion; + } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs new file mode 100644 index 000000000000..a084fa5580d3 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs @@ -0,0 +1,78 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System.Linq; +using System.Management.Automation.Language; +using System.Text; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Utitlities +{ + /// + /// A utility class for Az command line. + /// + internal static class CommandLineUtilities + { + /// + /// Masks the user input of any data, like names and locations. + /// Also alphabetizes the parameters to normalize them before sending + /// them to the model. + /// e.g., Get-AzContext -Name Hello -Location 'EastUS' => Get-AzContext -Location *** -Name *** + /// + /// The last user input command. + public static string MaskCommandLine(CommandAst cmdAst) + { + var commandElements = cmdAst?.CommandElements; + + if (commandElements == null) + { + return null; + } + + if (commandElements.Count == 1) + { + return cmdAst.Extent.Text; + } + + var sb = new StringBuilder(cmdAst.Extent.Text.Length); + _ = sb.Append(commandElements[0].ToString()); + var parameters = commandElements + .Skip(1) + .Where(element => element is CommandParameterAst) + .Cast() + .OrderBy(ast => ast.ParameterName); + + foreach (CommandParameterAst param in parameters) + { + _ = sb.Append(AzPredictorConstants.CommandParameterSeperator); + if (param.Argument != null) + { + // Parameter is in the form of `-Name:name` + _ = sb.Append(AzPredictorConstants.ParameterIndicator) + .Append(param.ParameterName) + .Append(AzPredictorConstants.ParameterValueSeperator) + .Append(AzPredictorConstants.ParameterValueMask); + } + else + { + // Parameter is in the form of `-Name` + _ = sb.Append(AzPredictorConstants.ParameterIndicator) + .Append(param.ParameterName) + .Append(AzPredictorConstants.CommandParameterSeperator) + .Append(AzPredictorConstants.ParameterValueMask); + } + } + return sb.ToString(); + } + } +} From 70b87204f7f3c1353d76d5c35ec6972237ef6b96 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Wed, 2 Dec 2020 16:19:34 -0800 Subject: [PATCH 07/15] Fix the SuggestionSource and test. - Updated the test after the refactor. - Add more test cases. - We don't set SuggestionSource on the suggestion in some cases. This is revealed in the unit tests. They're fixed. --- .../AzPredictorServiceTests.cs | 147 +++++++++++++++--- .../AzPredictorTests.cs | 24 ++- ...rTests.cs => CommandLinePredictorTests.cs} | 96 ++++++++---- .../Mocks/MockAzPredictorService.cs | 19 ++- .../Mocks/MockAzPredictorTelemetryClient.cs | 20 +-- .../Az.Tools.Predictor/AzPredictorService.cs | 7 +- 6 files changed, 240 insertions(+), 73 deletions(-) rename tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/{PredictorTests.cs => CommandLinePredictorTests.cs} (51%) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorServiceTests.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorServiceTests.cs index cd209ec4ba84..a2b7789af737 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorServiceTests.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorServiceTests.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------- // // Copyright Microsoft Corporation // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,6 +13,8 @@ // ---------------------------------------------------------------------------------- using Microsoft.Azure.PowerShell.Tools.AzPredictor.Test.Mocks; +using System; +using System.Collections.Generic; using System.Linq; using System.Management.Automation.Subsystem; using System.Threading; @@ -26,10 +28,36 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test [Collection("Model collection")] public class AzPredictorServiceTests { + private class PredictiveSuggestionComparer : EqualityComparer + { + public override bool Equals(PredictiveSuggestion first, PredictiveSuggestion second) + { + if ((first == null) && (second == null)) + { + return true; + } + else if ((first == null) || (second == null)) + { + return false; + } + + return string.Equals(first.SuggestionText, second.SuggestionText, StringComparison.Ordinal); + } + + public override int GetHashCode(PredictiveSuggestion suggestion) + { + return suggestion.SuggestionText.GetHashCode(); + } + } + private readonly ModelFixture _fixture; private readonly AzPredictorService _service; - private readonly Predictor _suggestionsPredictor; - private readonly Predictor _commandsPredictor; + private readonly CommandLinePredictor _commandBasedPredictor; + private readonly CommandLinePredictor _fallbackPredictor; + + private readonly AzPredictorService _noFallbackPredictorService; + private readonly AzPredictorService _noCommandBasedPredictorService; + private readonly AzPredictorService _noPredictorService; /// /// Constructs a new instance of @@ -39,15 +67,36 @@ public AzPredictorServiceTests(ModelFixture fixture) { this._fixture = fixture; var startHistory = $"{AzPredictorConstants.CommandPlaceholder}{AzPredictorConstants.CommandConcatenator}{AzPredictorConstants.CommandPlaceholder}"; - this._suggestionsPredictor = new Predictor(this._fixture.PredictionCollection[startHistory], null); - this._commandsPredictor = new Predictor(this._fixture.CommandCollection, null); + this._commandBasedPredictor = new CommandLinePredictor(this._fixture.PredictionCollection[startHistory], null); + this._fallbackPredictor = new CommandLinePredictor(this._fixture.CommandCollection, null); this._service = new MockAzPredictorService(startHistory, this._fixture.PredictionCollection[startHistory], this._fixture.CommandCollection); + + this._noFallbackPredictorService = new MockAzPredictorService(startHistory, this._fixture.PredictionCollection[startHistory], null); + this._noCommandBasedPredictorService = new MockAzPredictorService(null, null, this._fixture.CommandCollection); + this._noPredictorService = new MockAzPredictorService(null, null, null); } + /// + /// Verify the method checks parameter values. + /// + [Fact] + public void VerifyParameterValues() + { + var predictionContext = PredictionContext.Create("Get-AzContext"); + + Action actual = () => this._service.GetSuggestion(null, 1, 1, CancellationToken.None); + Assert.Throws(actual); + + actual = () => this._service.GetSuggestion(predictionContext.InputAst, 0, 1, CancellationToken.None); + Assert.Throws(actual); + + actual = () => this._service.GetSuggestion(predictionContext.InputAst, 1, 0, CancellationToken.None); + Assert.Throws(actual); + } /// - /// Verifies that the prediction comes from the suggestions list, not the command list. + /// Verifies that the prediction comes from the command based list, not the fallback list. /// [Theory] [InlineData("CONNECT-AZACCOUNT")] @@ -59,52 +108,104 @@ public AzPredictorServiceTests(ModelFixture fixture) [InlineData("new-azresourcegroup -name hello")] [InlineData("Get-AzContext -Name")] [InlineData("Get-AzContext -ErrorAction")] - public void VerifyUsingSuggestion(string userInput) + public void VerifyUsingCommandBasedPredictor(string userInput) { var predictionContext = PredictionContext.Create(userInput); var presentCommands = new System.Collections.Generic.Dictionary(); - var expected = this._suggestionsPredictor.Query(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var expected = this._commandBasedPredictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); var actual = this._service.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); - Assert.NotEmpty(actual); - Assert.NotNull(actual.First().Item1); - Assert.Equal(expected.Item1.First().Key, actual.First().Item1); - Assert.Equal(PredictionSource.CurrentCommand, actual.First().Item3); + Assert.NotNull(actual); + Assert.True(actual.Count > 0); + Assert.NotNull(actual.PredictiveSuggestions.First()); + Assert.NotNull(actual.PredictiveSuggestions.First().SuggestionText); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected.PredictiveSuggestions, actual.PredictiveSuggestions, new PredictiveSuggestionComparer()); + Assert.Equal(expected.SourceTexts, actual.SourceTexts); + Assert.All(actual.SuggestionSources, (source) => Assert.Equal(SuggestionSource.CurrentCommand, source)); + + actual = this._noFallbackPredictorService.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.Count > 0); + Assert.NotNull(actual.PredictiveSuggestions.First()); + Assert.NotNull(actual.PredictiveSuggestions.First().SuggestionText); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected.PredictiveSuggestions, actual.PredictiveSuggestions, new PredictiveSuggestionComparer()); + Assert.Equal(expected.SourceTexts, actual.SourceTexts); + Assert.All(actual.SuggestionSources, (source) => Assert.Equal(SuggestionSource.CurrentCommand, source)); } /// - /// Verifies that when no prediction is in the suggestion list, we'll use the command list. + /// Verifies that when no prediction is in the command based list, we'll use the fallback list. /// [Theory] [InlineData("Get-AzResource -Name hello -Pre")] [InlineData("Get-AzADServicePrincipal -ApplicationObject")] - public void VerifyUsingCommand(string userInput) + public void VerifyUsingFallbackPredictor(string userInput) { var predictionContext = PredictionContext.Create(userInput); var presentCommands = new System.Collections.Generic.Dictionary(); - var expected = this._commandsPredictor.Query(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var expected = this._fallbackPredictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); var actual = this._service.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); - Assert.NotEmpty(actual); - Assert.NotNull(actual.First().Item1); - Assert.Equal(expected.Item1.First().Key, actual.First().Item1); - Assert.Equal(PredictionSource.StaticCommands, actual.First().Item3); + Assert.NotNull(actual); + Assert.True(actual.Count > 0); + Assert.NotNull(actual.PredictiveSuggestions.First()); + Assert.NotNull(actual.PredictiveSuggestions.First().SuggestionText); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected.PredictiveSuggestions, actual.PredictiveSuggestions, new PredictiveSuggestionComparer()); + Assert.Equal(expected.SourceTexts, actual.SourceTexts); + Assert.All(actual.SuggestionSources, (source) => Assert.Equal(SuggestionSource.StaticCommands, source)); + + actual = this._noCommandBasedPredictorService.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.Count > 0); + Assert.NotNull(actual.PredictiveSuggestions.First()); + Assert.NotNull(actual.PredictiveSuggestions.First().SuggestionText); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected.PredictiveSuggestions, actual.PredictiveSuggestions, new PredictiveSuggestionComparer()); + Assert.Equal(expected.SourceTexts, actual.SourceTexts); + Assert.All(actual.SuggestionSources, (source) => Assert.Equal(SuggestionSource.StaticCommands, source)); } /// - /// Verify that no prediction for the user input, meaning it's not in the prediction list or the command list. + /// Verify that no prediction for the user input, meaning it's not in the command based list or the fallback list. /// [Theory] [InlineData(AzPredictorConstants.CommandPlaceholder)] - [InlineData("git status")] [InlineData("Get-ChildItem")] [InlineData("new-azresourcegroup -NoExistingParam")] [InlineData("get-azaccount ")] - [InlineData("Get-AzContext Name")] [InlineData("NEW-AZCONTEXT")] public void VerifyNoPrediction(string userInput) { var predictionContext = PredictionContext.Create(userInput); var actual = this._service.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); - Assert.Empty(actual); + Assert.Equal(0, actual.Count); + + actual = this._noFallbackPredictorService.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); + Assert.Equal(0, actual.Count); + + actual = this._noCommandBasedPredictorService.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); + Assert.Equal(0, actual.Count); + + actual = this._noPredictorService.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); + Assert.Null(actual); + } + + /// + /// Verify when we cannot parse the user input correctly. + /// + /// + /// When we can parse them correctly, please move the InlineData to the corresponding test methods, for example, "git status" + /// doesn't have any prediction so it should move to . + /// + [Theory] + [InlineData("git status")] + [InlineData("Get-AzContext Name")] + public void VerifyMalFormattedCommandLine(string userInput) + { + var predictionContext = PredictionContext.Create(userInput); + Action actual = () => this._service.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); + _ = Assert.Throws(actual); } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTests.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTests.cs index b8dd8a854d6c..4032b733f235 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTests.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTests.cs @@ -45,6 +45,7 @@ public AzPredictorTests(ModelFixture modelFixture) this._azPredictor = new AzPredictor(this._service, this._telemetryClient, new Settings() { SuggestionCount = 1, + MaxAllowedCommandDuplicate = 1, }, null); } @@ -134,7 +135,6 @@ public void VerifySupportedCommandMasked() /// Verifies AzPredictor returns the same value as AzPredictorService for the prediction. /// [Theory] - [InlineData("git status")] [InlineData("new-azresourcegroup -name hello")] [InlineData("Get-AzContext -Name")] [InlineData("Get-AzContext -ErrorAction")] @@ -145,7 +145,8 @@ public void VerifySuggestion(string userInput) var expected = this._service.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); var actual = this._azPredictor.GetSuggestion(predictionContext, CancellationToken.None); - Assert.Equal(expected.Select(e => e.Item1), actual.Select(a => a.SuggestionText)); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected.PredictiveSuggestions.First().SuggestionText, actual.First().SuggestionText); } /// @@ -158,6 +159,7 @@ public void VerifySuggestionOnIncompleteCommand() var localAzPredictor = new AzPredictor(this._service, this._telemetryClient, new Settings() { SuggestionCount = 7, + MaxAllowedCommandDuplicate = 1, }, null); @@ -169,5 +171,23 @@ public void VerifySuggestionOnIncompleteCommand() Assert.Equal(expected, actual.First().SuggestionText); } + + + /// + /// Verify when we cannot parse the user input correctly. + /// + /// + /// When we can parse them correctly, please move the InlineData to the corresponding test methods, for example, "git status" + /// can be moved to . + /// + [Theory] + [InlineData("git status")] + public void VerifyMalFormattedCommandLine(string userInput) + { + var predictionContext = PredictionContext.Create(userInput); + var actual = this._azPredictor.GetSuggestion(predictionContext, CancellationToken.None); + + Assert.Empty(actual); + } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/PredictorTests.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/CommandLinePredictorTests.cs similarity index 51% rename from tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/PredictorTests.cs rename to tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/CommandLinePredictorTests.cs index 37f7862ed376..1f2c9b32cdcc 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/PredictorTests.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/CommandLinePredictorTests.cs @@ -12,6 +12,8 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using System; +using System.Collections.Generic; using System.Linq; using System.Management.Automation.Subsystem; using System.Threading; @@ -20,22 +22,44 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test { /// - /// Test cases for + /// Test cases for /// [Collection("Model collection")] - public class PredictorTests + public class CommandLinePredictorTests { private readonly ModelFixture _fixture; - private readonly Predictor _predictor; + private readonly CommandLinePredictor _predictor; /// - /// Constructs a new instance of + /// Constructs a new instance of /// - public PredictorTests(ModelFixture fixture) + public CommandLinePredictorTests(ModelFixture fixture) { this._fixture = fixture; var startHistory = $"{AzPredictorConstants.CommandPlaceholder}{AzPredictorConstants.CommandConcatenator}{AzPredictorConstants.CommandPlaceholder}"; - this._predictor = new Predictor(this._fixture.PredictionCollection[startHistory], null); + this._predictor = new CommandLinePredictor(this._fixture.PredictionCollection[startHistory], null); + } + + /// + /// Verify the method checks parameter values. + /// + [Fact] + public void VerifyParameterValues() + { + var predictionContext = PredictionContext.Create("Get-AzContext"); + var presentCommands = new Dictionary(); + + Action actual = () => this._predictor.GetSuggestion(null, presentCommands, 1, 1, CancellationToken.None); + Assert.Throws(actual); + + actual = () => this._predictor.GetSuggestion(predictionContext.InputAst, null, 1, 1, CancellationToken.None); + Assert.Throws(actual); + + actual = () => this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 0, 1, CancellationToken.None); + Assert.Throws(actual); + + actual = () => this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 0, CancellationToken.None); + Assert.Throws(actual); } /// @@ -45,14 +69,13 @@ public PredictorTests(ModelFixture fixture) [InlineData("NEW-AZCONTEXT")] [InlineData("get-azaccount ")] [InlineData(AzPredictorConstants.CommandPlaceholder)] - [InlineData("git status")] [InlineData("Get-ChildItem")] - public void GetNullPredictionWithCommandName(string userInput) + public void GetNoPredictionWithCommandName(string userInput) { var predictionContext = PredictionContext.Create(userInput); - var presentCommands = new System.Collections.Generic.Dictionary(); - var result = this._predictor.Query(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); - Assert.Empty(result.Item1); + var presentCommands = new Dictionary(); + var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + Assert.Equal(0, result.Count); } /// @@ -67,9 +90,9 @@ public void GetNullPredictionWithCommandName(string userInput) public void GetPredictionWithCommandName(string userInput) { var predictionContext = PredictionContext.Create(userInput); - var presentCommands = new System.Collections.Generic.Dictionary(); - var result = this._predictor.Query(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); - Assert.NotEmpty(result.Item1); + var presentCommands = new Dictionary(); + var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + Assert.True(result.Count > 0); } /// @@ -83,9 +106,9 @@ public void GetPredictionWithCommandName(string userInput) public void GetPredictionWithCommandNameParameters(string userInput) { var predictionContext = PredictionContext.Create(userInput); - var presentCommands = new System.Collections.Generic.Dictionary(); - var result = this._predictor.Query(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); - Assert.NotEmpty(result.Item1); + var presentCommands = new Dictionary(); + var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + Assert.True(result.Count > 0); } /// @@ -96,13 +119,12 @@ public void GetPredictionWithCommandNameParameters(string userInput) [InlineData("Get-AzADServicePrincipal -ApplicationObject")] // Doesn't exist [InlineData("new-azresourcegroup -NoExistingParam")] [InlineData("Set-StorageAccount -WhatIf")] - [InlineData("Get-AzContext Name")] // a wrong command - public void GetNullPredictionWithCommandNameParameters(string userInput) + public void GetNoPredictionWithCommandNameParameters(string userInput) { var predictionContext = PredictionContext.Create(userInput); - var presentCommands = new System.Collections.Generic.Dictionary(); - var result = this._predictor.Query(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); - Assert.Empty(result.Item1); + var presentCommands = new Dictionary(); + var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + Assert.Equal(0, result.Count); } /// @@ -112,10 +134,10 @@ public void GetNullPredictionWithCommandNameParameters(string userInput) public void VerifyPredictionForCommand() { var predictionContext = PredictionContext.Create("Connect-AzAccount"); - var presentCommands = new System.Collections.Generic.Dictionary(); - var result = this._predictor.Query(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var presentCommands = new Dictionary(); + var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); - Assert.Equal("Connect-AzAccount -Credential -ServicePrincipal -Tenant <>", result.Item1.First().Key); + Assert.Equal("Connect-AzAccount -Credential -ServicePrincipal -Tenant <>", result.PredictiveSuggestions.First().SuggestionText); } /// @@ -125,10 +147,28 @@ public void VerifyPredictionForCommand() public void VerifyPredictionForCommandAndParameters() { var predictionContext = PredictionContext.Create("GET-AZSTORAGEACCOUNTKEY -NAME"); - var presentCommands = new System.Collections.Generic.Dictionary(); - var result = this._predictor.Query(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var presentCommands = new Dictionary(); + var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + + Assert.Equal("Get-AzStorageAccountKey -Name 'ContosoStorage' -ResourceGroupName 'ContosoGroup02'", result.PredictiveSuggestions.First().SuggestionText); + } - Assert.Equal("Get-AzStorageAccountKey -Name 'ContosoStorage' -ResourceGroupName 'ContosoGroup02'", result.Item1.First().Key); + /// + /// Verify when we cannot parse the user input correctly. + /// + /// + /// When we can parse them correctly, please move the InlineData to the corresponding test methods, for example, "git status" + /// doesn't have any prediction so it should move to . + /// + [Theory] + [InlineData("git status")] + [InlineData("Get-AzContext Name")] // a wrong command + public void VerifyMalFormattedCommandLine(string userInput) + { + var predictionContext = PredictionContext.Create(userInput); + var presentCommands = new Dictionary(); + Action actual = () => this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + _ = Assert.Throws(actual); } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorService.cs index 8c9fd8e5ff27..334aa5d01ed6 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorService.cs @@ -34,9 +34,20 @@ sealed class MockAzPredictorService : AzPredictorService /// The commands collection public MockAzPredictorService(string history, IList suggestions, IList commands) { - SetPredictionCommand(history); - SetCommandsPredictor(commands); - SetSuggestionPredictor(history, suggestions); + if (history != null) + { + SetCommandToRequestPrediction(history); + + if (suggestions != null) + { + SetCommandBasedPreditor(history, suggestions); + } + } + + if (commands != null) + { + SetFallbackPredictor(commands); + } } /// @@ -46,7 +57,7 @@ public override void RequestPredictions(IEnumerable history) } /// - protected override void RequestCommands() + protected override void RequestAllPredictiveCommands() { // Do nothing since we've set the command and suggestion predictors. } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs index cd51a155ebe8..ff68ee39b9d0 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs @@ -13,7 +13,6 @@ // ---------------------------------------------------------------------------------- using System; -using System.Collections.Generic; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test.Mocks { @@ -34,38 +33,29 @@ public class RecordedSuggestionForHistory public int SuggestionAccepted { get; set; } /// - public void OnHistory(string historyLine) + public void OnHistory(TelemetryData.History telemetryData) { this.RecordedSuggestion = new RecordedSuggestionForHistory() { - HistoryLine = historyLine, + HistoryLine = telemetryData.Command, }; } /// - public void OnRequestPrediction(string command) + public void OnRequestPrediction(TelemetryData.RequestPrediction telemetryData) { } /// - public void OnRequestPredictionError(string command, Exception e) - { - } - - /// - public void OnSuggestionAccepted(string acceptedSuggestion) + public void OnSuggestionAccepted(TelemetryData.SuggestionAccepted telemetryData) { ++this.SuggestionAccepted; } /// - public void OnGetSuggestion(string maskedUserInput, IEnumerable> suggestions, bool isCancelled) + public void OnGetSuggestion(TelemetryData.GetSuggestion telemetryData) { } - /// - public void OnGetSuggestionError(Exception e) - { - } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index e82b9502bc85..21697696d882 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -178,9 +178,14 @@ public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int m var suggestionCountToRequest = (result == null) ? suggestionCount : suggestionCount - result.Count; var resultsFromFallback = fallbackPredictor?.GetSuggestion(input, presentCommands, suggestionCountToRequest, maxAllowedCommandDuplicate, cancellationToken); - if (result == null) + if ((result == null) && (resultsFromFallback != null)) { result = resultsFromFallback; + + for (var i = 0; i < result.Count; ++i) + { + result.UpdateSuggestionSource(i, SuggestionSource.StaticCommands); + } } else if ((resultsFromFallback != null) && (resultsFromFallback.Count > 0)) { From c4d76548337824f3e705e0de638adf7cea4c9e3f Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Wed, 2 Dec 2020 16:57:09 -0800 Subject: [PATCH 08/15] Avoid duplicate extraction of user input. - We have two CommandLinePredictor in AzurePredictorService. The CommandLinePredictor needs to extract from the user input the command name, parameter set etc. It's duplicate if we do that in both CommandLinePredictor. Move that extraction to AzurePredictorService and the CommandLinePredictor will not need to do it. --- .../AzPredictorServiceTests.cs | 31 +++- .../CommandLinePredictorTests.cs | 148 ++++++++++++++---- .../Az.Tools.Predictor/AzPredictorService.cs | 32 +++- .../CommandLinePredictor.cs | 33 ++-- .../Az.Tools.Predictor/ParameterSet.cs | 1 - 5 files changed, 191 insertions(+), 54 deletions(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorServiceTests.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorServiceTests.cs index a2b7789af737..262279c87903 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorServiceTests.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorServiceTests.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Management.Automation.Language; using System.Management.Automation.Subsystem; using System.Threading; using Xunit; @@ -111,8 +112,19 @@ public void VerifyParameterValues() public void VerifyUsingCommandBasedPredictor(string userInput) { var predictionContext = PredictionContext.Create(userInput); - var presentCommands = new System.Collections.Generic.Dictionary(); - var expected = this._commandBasedPredictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; + var presentCommands = new Dictionary(); + var expected = this._commandBasedPredictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); + var actual = this._service.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.Count > 0); @@ -143,8 +155,19 @@ public void VerifyUsingCommandBasedPredictor(string userInput) public void VerifyUsingFallbackPredictor(string userInput) { var predictionContext = PredictionContext.Create(userInput); - var presentCommands = new System.Collections.Generic.Dictionary(); - var expected = this._fallbackPredictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; + var presentCommands = new Dictionary(); + var expected = this._fallbackPredictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); + var actual = this._service.GetSuggestion(predictionContext.InputAst, 1, 1, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.Count > 0); diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/CommandLinePredictorTests.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/CommandLinePredictorTests.cs index 1f2c9b32cdcc..dd2bd9a6c32d 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/CommandLinePredictorTests.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/CommandLinePredictorTests.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Management.Automation.Language; using System.Management.Automation.Subsystem; using System.Threading; using Xunit; @@ -47,18 +48,64 @@ public CommandLinePredictorTests(ModelFixture fixture) public void VerifyParameterValues() { var predictionContext = PredictionContext.Create("Get-AzContext"); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; var presentCommands = new Dictionary(); - Action actual = () => this._predictor.GetSuggestion(null, presentCommands, 1, 1, CancellationToken.None); + Action actual = () => this._predictor.GetSuggestion(null, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); + Assert.Throws(actual); + + actual = () => this._predictor.GetSuggestion(commandName, + null, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); Assert.Throws(actual); - actual = () => this._predictor.GetSuggestion(predictionContext.InputAst, null, 1, 1, CancellationToken.None); + actual = () => this._predictor.GetSuggestion(commandName, + inputParameterSet, + null, + presentCommands, + 1, + 1, + CancellationToken.None); + Assert.Throws(actual); + + actual = () => this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + null, + 1, + 1, + CancellationToken.None); Assert.Throws(actual); - actual = () => this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 0, 1, CancellationToken.None); + actual = () => this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 0, + 1, + CancellationToken.None); Assert.Throws(actual); - actual = () => this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 0, CancellationToken.None); + actual = () => this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 0, + CancellationToken.None); Assert.Throws(actual); } @@ -73,8 +120,18 @@ public void VerifyParameterValues() public void GetNoPredictionWithCommandName(string userInput) { var predictionContext = PredictionContext.Create(userInput); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; var presentCommands = new Dictionary(); - var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var result = this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); Assert.Equal(0, result.Count); } @@ -90,8 +147,18 @@ public void GetNoPredictionWithCommandName(string userInput) public void GetPredictionWithCommandName(string userInput) { var predictionContext = PredictionContext.Create(userInput); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; var presentCommands = new Dictionary(); - var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var result = this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); Assert.True(result.Count > 0); } @@ -106,8 +173,18 @@ public void GetPredictionWithCommandName(string userInput) public void GetPredictionWithCommandNameParameters(string userInput) { var predictionContext = PredictionContext.Create(userInput); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; var presentCommands = new Dictionary(); - var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var result = this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); Assert.True(result.Count > 0); } @@ -119,11 +196,24 @@ public void GetPredictionWithCommandNameParameters(string userInput) [InlineData("Get-AzADServicePrincipal -ApplicationObject")] // Doesn't exist [InlineData("new-azresourcegroup -NoExistingParam")] [InlineData("Set-StorageAccount -WhatIf")] + // Enable "git status" and "Get-AzContext Name" when ParameterSet can parse this format of command + // [InlineData("git status")] + // [InlineData("Get-AzContext Name")] // a wrong command public void GetNoPredictionWithCommandNameParameters(string userInput) { var predictionContext = PredictionContext.Create(userInput); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; var presentCommands = new Dictionary(); - var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var result = this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); Assert.Equal(0, result.Count); } @@ -134,8 +224,18 @@ public void GetNoPredictionWithCommandNameParameters(string userInput) public void VerifyPredictionForCommand() { var predictionContext = PredictionContext.Create("Connect-AzAccount"); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; var presentCommands = new Dictionary(); - var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var result = this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); Assert.Equal("Connect-AzAccount -Credential -ServicePrincipal -Tenant <>", result.PredictiveSuggestions.First().SuggestionText); } @@ -147,28 +247,20 @@ public void VerifyPredictionForCommand() public void VerifyPredictionForCommandAndParameters() { var predictionContext = PredictionContext.Create("GET-AZSTORAGEACCOUNTKEY -NAME"); + var commandAst = predictionContext.InputAst.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = predictionContext.InputAst.Extent.Text; var presentCommands = new Dictionary(); - var result = this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); + var result = this._predictor.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + 1, + 1, + CancellationToken.None); Assert.Equal("Get-AzStorageAccountKey -Name 'ContosoStorage' -ResourceGroupName 'ContosoGroup02'", result.PredictiveSuggestions.First().SuggestionText); } - - /// - /// Verify when we cannot parse the user input correctly. - /// - /// - /// When we can parse them correctly, please move the InlineData to the corresponding test methods, for example, "git status" - /// doesn't have any prediction so it should move to . - /// - [Theory] - [InlineData("git status")] - [InlineData("Get-AzContext Name")] // a wrong command - public void VerifyMalFormattedCommandLine(string userInput) - { - var predictionContext = PredictionContext.Create(userInput); - var presentCommands = new Dictionary(); - Action actual = () => this._predictor.GetSuggestion(predictionContext.InputAst, presentCommands, 1, 1, CancellationToken.None); - _ = Assert.Throws(actual); - } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index 21697696d882..9bcdd07aa70f 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -151,17 +151,33 @@ public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int m Validation.CheckArgument(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0."); Validation.CheckArgument(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0."); - var commandBasedPredictor = _commandBasedPredictor; - var command = _commandToRequestPrediction; + var commandAst = input.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; + var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; + + if (string.IsNullOrWhiteSpace(commandName)) + { + return null; + } + var inputParameterSet = new ParameterSet(commandAst); + var rawUserInput = input.Extent.Text; var presentCommands = new Dictionary(); - var result = commandBasedPredictor?.Item2?.GetSuggestion(input, presentCommands, suggestionCount, maxAllowedCommandDuplicate, cancellationToken); + var commandBasedPredictor = _commandBasedPredictor; + var commandToRequestPrediction = _commandToRequestPrediction; + + var result = commandBasedPredictor?.Item2?.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + suggestionCount, + maxAllowedCommandDuplicate, + cancellationToken); if ((result != null) && (result.Count > 0)) { var suggestionSource = SuggestionSource.PreviousCommand; - if (string.Equals(command, commandBasedPredictor?.Item1, StringComparison.Ordinal)) + if (string.Equals(commandToRequestPrediction, commandBasedPredictor?.Item1, StringComparison.Ordinal)) { suggestionSource = SuggestionSource.CurrentCommand; } @@ -176,7 +192,13 @@ public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int m { var fallbackPredictor = _fallbackPredictor; var suggestionCountToRequest = (result == null) ? suggestionCount : suggestionCount - result.Count; - var resultsFromFallback = fallbackPredictor?.GetSuggestion(input, presentCommands, suggestionCountToRequest, maxAllowedCommandDuplicate, cancellationToken); + var resultsFromFallback = fallbackPredictor?.GetSuggestion(commandName, + inputParameterSet, + rawUserInput, + presentCommands, + suggestionCountToRequest, + maxAllowedCommandDuplicate, + cancellationToken); if ((result == null) && (resultsFromFallback != null)) { diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index e71df5ccb126..24e25391a8e9 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -65,37 +65,38 @@ public CommandLinePredictor(IList modelPredictions, ParameterValuePredic /// /// Returns suggestions given the user input. /// - /// PowerShell AST input of the user, generated by PSReadLine + /// The command name extracted from the user input. + /// The parameter set extracted from the user input. + /// The string format of the command line from user input. /// Commands already present. Contents may be added to this collection. /// The number of suggestions to return. /// The maximum amount of the same commnds in the list of predictions. /// The cancellation token /// The collections of suggestions. - public CommandLineSuggestion GetSuggestion(Ast input, IDictionary presentCommands, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken) + public CommandLineSuggestion GetSuggestion(string inputCommandName, + ParameterSet inputParameterSet, + string rawUserInput, + IDictionary presentCommands, + int suggestionCount, + int maxAllowedCommandDuplicate, + CancellationToken cancellationToken) { - Validation.CheckArgument(input, $"{nameof(input)} cannot be null."); + Validation.CheckArgument(!string.IsNullOrWhiteSpace(inputCommandName), $"{nameof(inputCommandName)} cannot be null or whitespace."); + Validation.CheckArgument(inputParameterSet, $"{nameof(inputParameterSet)} cannot be null."); + Validation.CheckArgument(!string.IsNullOrWhiteSpace(rawUserInput), $"{nameof(rawUserInput)} cannot be null or whitespace."); Validation.CheckArgument(presentCommands, $"{nameof(presentCommands)} cannot be null."); Validation.CheckArgument(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0."); Validation.CheckArgument(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0."); - var commandAst = input.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst; - var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value; - - if (string.IsNullOrWhiteSpace(commandName)) - { - return null; - } - CommandLineSuggestion result = new(); var resultsTemp = new Dictionary(StringComparer.OrdinalIgnoreCase); - var inputParameterSet = new ParameterSet(commandAst); - var isCommandNameComplete = (((commandAst?.CommandElements != null) && (commandAst.CommandElements.Count > 1)) || ((input as ScriptBlockAst)?.Extent?.Text?.EndsWith(' ') == true)); + var isCommandNameComplete = inputParameterSet.Parameters.Any() || rawUserInput.EndsWith(' '); - Func commandNameQuery = (command) => command.Equals(commandName, StringComparison.OrdinalIgnoreCase); + Func commandNameQuery = (command) => command.Equals(inputCommandName, StringComparison.OrdinalIgnoreCase); if (!isCommandNameComplete) { - commandNameQuery = (command) => command.StartsWith(commandName, StringComparison.OrdinalIgnoreCase); + commandNameQuery = (command) => command.StartsWith(inputCommandName, StringComparison.OrdinalIgnoreCase); } // Try to find the matching command and arrange the parameters in the order of the input. @@ -125,7 +126,7 @@ public CommandLineSuggestion GetSuggestion(Ast input, IDictionary p PredictRestOfParameters(resultBuilder, _commandLinePredictions[i].ParameterSet.Parameters, usedParams); var prediction = UnescapePredictionText(resultBuilder); - if (prediction.Length <= input.Extent.Text.Length) + if (prediction.Length <= rawUserInput.Length) { continue; } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs index e24a0328e84e..91204c7a5949 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs @@ -13,7 +13,6 @@ // ---------------------------------------------------------------------------------- using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; -using System; using System.Collections.Generic; using System.Linq; using System.Management.Automation.Language; From 0fc0ca28faa103228a52b4e33884740af2697ca6 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Wed, 2 Dec 2020 17:16:27 -0800 Subject: [PATCH 09/15] Fix a bug that a duplicate key is in the dictionary. --- .../Az.Tools.Predictor/CommandLinePredictor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index 24e25391a8e9..6993088b9614 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -159,7 +159,7 @@ public CommandLineSuggestion GetSuggestion(string inputCommandName, } else { - resultsTemp.Add(prediction.ToString(), sourceBuilder.ToString()); + _ = resultsTemp.TryAdd(prediction.ToString(), sourceBuilder.ToString()); } if (result.Count == suggestionCount) From b2a8867b7ec8d396f7735da302f5a19f409a08fe Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Thu, 3 Dec 2020 11:59:44 -0800 Subject: [PATCH 10/15] Replace Newtonsoft with the built-in Json serializer. --- .../Az.Tools.Predictor.Test/ModelFixture.cs | 7 +- .../Az.Tools.Predictor/AzPredictor.cs | 2 +- .../Az.Tools.Predictor/AzPredictorService.cs | 15 ++-- .../AzPredictorTelemetryClient.cs | 6 +- .../Profile/AzurePSDataCollectionProfile.cs | 13 ++-- .../Az.Tools.Predictor/SuggestionSource.cs | 4 - .../Az.Tools.Predictor/TelemetryData.cs | 1 - .../Utilities/CommandLineUtilities.cs | 2 +- .../Utilities/JsonUtilities.cs | 73 +++++++++++++++++++ 9 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelFixture.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelFixture.cs index 3f029d6d6272..74538f27a8f6 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelFixture.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/ModelFixture.cs @@ -12,11 +12,12 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using Newtonsoft.Json; +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Text.Json; using Xunit; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test @@ -54,8 +55,8 @@ public ModelFixture() var commandsModel = ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.CommandsModelZip), ModelFixture.CommandsModelJson); var predictionsModel = ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.PredictionsModelZip), ModelFixture.PredictionsModelJson); - this.CommandCollection = JsonConvert.DeserializeObject>(commandsModel); - this.PredictionCollection = JsonConvert.DeserializeObject>>(predictionsModel); + this.CommandCollection = JsonSerializer.Deserialize>(commandsModel, JsonUtilities.DefaultSerializerOptions); + this.PredictionCollection = JsonSerializer.Deserialize>>(predictionsModel, JsonUtilities.DefaultSerializerOptions); } /// diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs index 5b84e7a61b87..0a840ca4868c 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs @@ -12,7 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utitlities; +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.Collections.Generic; using System.Linq; diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index 9bcdd07aa70f..fdbfc69c55f0 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -13,8 +13,6 @@ // ---------------------------------------------------------------------------------- using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Linq; @@ -22,6 +20,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -34,7 +33,6 @@ internal class AzPredictorService : IAzPredictorService, IDisposable { private const string ClientType = "AzurePowerShell"; - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] private sealed class PredictionRequestBody { public sealed class RequestContext @@ -52,7 +50,6 @@ public sealed class RequestContext public PredictionRequestBody(string command) => History = command; }; - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] private sealed class CommandRequestContext { public Version VersionNumber{ get; set; } = new Version(0, 0); @@ -100,7 +97,7 @@ public AzPredictorService(string serviceUri, ITelemetryClient telemetryClient, I Validation.CheckArgument(telemetryClient, $"{nameof(telemetryClient)} cannot be null."); Validation.CheckArgument(azContext, $"{nameof(azContext)} cannot be null."); - _commandsEndpoint = $"{serviceUri}{AzPredictorConstants.CommandsEndpoint}?clientType={AzPredictorService.ClientType}&context={JsonConvert.SerializeObject(new CommandRequestContext())}"; + _commandsEndpoint = $"{serviceUri}{AzPredictorConstants.CommandsEndpoint}?clientType={AzPredictorService.ClientType}&context={JsonSerializer.Serialize(new CommandRequestContext(), JsonUtilities.DefaultSerializerOptions)}"; _predictionsEndpoint = serviceUri + AzPredictorConstants.PredictionsEndpoint; _telemetryClient = telemetryClient; _azContext = azContext; @@ -273,12 +270,12 @@ public virtual void RequestPredictions(IEnumerable commands) Context = requestContext, }; - var requestBodyString = JsonConvert.SerializeObject(requestBody); + var requestBodyString = JsonSerializer.Serialize(requestBody, JsonUtilities.DefaultSerializerOptions); var httpResponseMessage = await _client.PostAsync(_predictionsEndpoint, new StringContent(requestBodyString, Encoding.UTF8, "application/json"), cancellationToken); postSuccess = true; - var reply = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); - var suggestionsList = JsonConvert.DeserializeObject>(reply); + var reply = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken); + var suggestionsList = await JsonSerializer.DeserializeAsync>(reply, JsonUtilities.DefaultSerializerOptions); SetCommandBasedPreditor(localCommands, suggestionsList); } @@ -332,7 +329,7 @@ protected virtual void RequestAllPredictiveCommands() var httpResponseMessage = await _client.GetAsync(_commandsEndpoint); var reply = await httpResponseMessage.Content.ReadAsStringAsync(); - var commandsReply = JsonConvert.DeserializeObject>(reply); + var commandsReply = JsonSerializer.Deserialize>(reply, JsonUtilities.DefaultSerializerOptions); SetFallbackPredictor(commandsReply); // Initialize predictions diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs index b3ce7c7c568b..d9df86c54cb1 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs @@ -15,13 +15,13 @@ using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.Azure.PowerShell.Tools.AzPredictor.Profile; -using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utitlities; -using Newtonsoft.Json; +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Management.Automation.Language; +using System.Text.Json; using System.Threading.Tasks.Dataflow; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor @@ -235,7 +235,7 @@ private void SendTelemetry(TelemetryData.GetSuggestion telemetryData) var properties = CreateProperties(telemetryData); properties.Add("UserInput", maskedUserInput ?? string.Empty); - properties.Add("Suggestion", sourceTexts != null ? JsonConvert.SerializeObject(sourceTexts.Zip(suggestionSource).Select((s) => ValueTuple.Create(s.First, s.Second))) : string.Empty); + properties.Add("Suggestion", sourceTexts != null ? JsonSerializer.Serialize(sourceTexts.Zip(suggestionSource).Select((s) => Tuple.Create(s.First, s.Second)), JsonUtilities.TelemetrySerializerOptions) : string.Empty); properties.Add("IsCancelled", telemetryData.IsCancellationRequested.ToString(CultureInfo.InvariantCulture)); properties.Add("Exception", telemetryData.Exception?.ToString() ?? string.Empty); diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Profile/AzurePSDataCollectionProfile.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Profile/AzurePSDataCollectionProfile.cs index a31b7c1d319a..ab4fc91e4aa4 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Profile/AzurePSDataCollectionProfile.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Profile/AzurePSDataCollectionProfile.cs @@ -12,10 +12,10 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using Microsoft.Azure.PowerShell.Tools.AzPredictor; -using Newtonsoft.Json; +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.IO; +using System.Text.Json; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Profile { @@ -24,7 +24,7 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Profile /// /// The profile about data collection in Azure PowerShell /// - public sealed class AzurePSDataCollectionProfile + internal sealed class AzurePSDataCollectionProfile { private const string EnvironmentVariableName = "Azure_PS_Data_Collection"; private const string DefaultFileName = "AzurePSDataCollectionProfile.json"; @@ -59,10 +59,9 @@ private AzurePSDataCollectionProfile(bool enable) } /// - /// Gets if the data collection is enabled. + /// Gets or sets if the data collection is enabled. /// - [JsonProperty(PropertyName = "enableAzureDataCollection")] - public bool? EnableAzureDataCollection { get; private set; } + public bool? EnableAzureDataCollection { get; set; } private static AzurePSDataCollectionProfile CreateInstance() { @@ -90,7 +89,7 @@ private static AzurePSDataCollectionProfile CreateInstance() if (File.Exists(dataPath)) { string contents = File.ReadAllText(dataPath); - var localResult = JsonConvert.DeserializeObject(contents); + var localResult = JsonSerializer.Deserialize(contents, JsonUtilities.DefaultSerializerOptions); if (localResult != null && localResult.EnableAzureDataCollection.HasValue) { result = localResult; diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/SuggestionSource.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/SuggestionSource.cs index cdb890300a00..f4304b99f8f6 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/SuggestionSource.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/SuggestionSource.cs @@ -12,15 +12,11 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { /// /// An enum for the source where we get the suggestion. /// - [JsonConverter(typeof(StringEnumConverter))] public enum SuggestionSource { /// diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs index 71fed343d169..5513be7f9fec 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs @@ -14,7 +14,6 @@ using System; using System.Management.Automation.Language; -using System.Threading; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor { diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs index a084fa5580d3..db9be23e6911 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs @@ -16,7 +16,7 @@ using System.Management.Automation.Language; using System.Text; -namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Utitlities +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities { /// /// A utility class for Az command line. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs new file mode 100644 index 000000000000..2b8f5a0f22c9 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs @@ -0,0 +1,73 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities +{ + /// + /// A utility class for json serialization/deserialization. + /// + internal static class JsonUtilities + { + private sealed class VersionConverter : JsonConverter + { + public override Version Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (Version.TryParse(reader.GetString(), out var version)) + { + return version; + } + + throw new JsonException(); + } + + public override void Write (Utf8JsonWriter writer, Version value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } + + /// + /// The default serialization options: + /// 1. Use camel case in the naming. + /// 2. Use string instead of number for enums. + /// + public static readonly JsonSerializerOptions DefaultSerializerOptions = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), + new VersionConverter(), + }, + }; + + /// + /// The serialization options for sending the telemetry. + /// + /// + /// The options are based on except: + /// 1. Uses The result is treated as a string in the + /// telemetry and we don't want to use the default encoder which escape characters such as ', ", <, >, +. + /// + public static readonly JsonSerializerOptions TelemetrySerializerOptions = new JsonSerializerOptions(JsonUtilities.DefaultSerializerOptions) + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + } +} From 0711921a860d15482638d5ade9ba14d82d7e9984 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Thu, 3 Dec 2020 14:31:59 -0800 Subject: [PATCH 11/15] Fix an issue that the parameter name is repeated in source text. --- .../Az.Tools.Predictor/CommandLinePredictor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index 6993088b9614..df4ece05e0b1 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -142,11 +142,10 @@ public CommandLineSuggestion GetSuggestion(string inputCommandName, if (!string.IsNullOrWhiteSpace(p.Value)) { _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = sourceBuilder.Append(p.Name); + _ = sourceBuilder.Append(p.Value); } } - if (!presentCommands.ContainsKey(_commandLinePredictions[i].Name)) { result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); From 84fa5a624ce6707b3b8f0f06d33477fbf64d3c02 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Thu, 3 Dec 2020 16:54:10 -0800 Subject: [PATCH 12/15] Ensure the http response is successful. --- .../Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index fdbfc69c55f0..a8fa7ca87d4d 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -274,6 +274,7 @@ public virtual void RequestPredictions(IEnumerable commands) var httpResponseMessage = await _client.PostAsync(_predictionsEndpoint, new StringContent(requestBodyString, Encoding.UTF8, "application/json"), cancellationToken); postSuccess = true; + httpResponseMessage.EnsureSuccessStatusCode(); var reply = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken); var suggestionsList = await JsonSerializer.DeserializeAsync>(reply, JsonUtilities.DefaultSerializerOptions); @@ -328,6 +329,7 @@ protected virtual void RequestAllPredictiveCommands() var httpResponseMessage = await _client.GetAsync(_commandsEndpoint); + httpResponseMessage.EnsureSuccessStatusCode(); var reply = await httpResponseMessage.Content.ReadAsStringAsync(); var commandsReply = JsonSerializer.Deserialize>(reply, JsonUtilities.DefaultSerializerOptions); SetFallbackPredictor(commandsReply); From d4f069918b00f64f894d3ded79f565162c604ae6 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Fri, 4 Dec 2020 09:13:41 -0800 Subject: [PATCH 13/15] Collect the event in requesting to /commands. --- .../Az.Tools.Predictor/AzPredictorService.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index a8fa7ca87d4d..c6524ad74c50 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -325,14 +325,27 @@ protected virtual void RequestAllPredictiveCommands() // We don't need to block on the task. We send the HTTP request and update commands and predictions list at the background. Task.Run(async () => { - _client.DefaultRequestHeaders?.Add(AzPredictorService.ThrottleByIdHeader, _azContext.UserId); + Exception exception = null; - var httpResponseMessage = await _client.GetAsync(_commandsEndpoint); + try + { + _client.DefaultRequestHeaders?.Add(AzPredictorService.ThrottleByIdHeader, _azContext.UserId); + + var httpResponseMessage = await _client.GetAsync(_commandsEndpoint); - httpResponseMessage.EnsureSuccessStatusCode(); - var reply = await httpResponseMessage.Content.ReadAsStringAsync(); - var commandsReply = JsonSerializer.Deserialize>(reply, JsonUtilities.DefaultSerializerOptions); - SetFallbackPredictor(commandsReply); + httpResponseMessage.EnsureSuccessStatusCode(); + var reply = await httpResponseMessage.Content.ReadAsStringAsync(); + var commandsReply = JsonSerializer.Deserialize>(reply, JsonUtilities.DefaultSerializerOptions); + SetFallbackPredictor(commandsReply); + } + catch (Exception e) + { + exception = e; + } + finally + { + _telemetryClient.OnRequestPrediction(new TelemetryData.RequestPrediction("request_commands", hasSentHttpRequest: true, exception: exception)); + } // Initialize predictions RequestPredictions(new string[] { From ed6cd4e550286d751ddafb5bc6597dc5e7684f81 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Thu, 3 Dec 2020 16:37:31 -0800 Subject: [PATCH 14/15] Improve the perf in GetSuggestion. - Remove the string manipulation. - Pre-allocate the collections for the result. - Remove invariant check in "readonly" properties. --- .../CommandLinePredictor.cs | 77 +++++++------------ .../CommandLineSuggestion.cs | 20 +++-- .../Az.Tools.Predictor/ParameterSet.cs | 30 ++++---- .../ParameterValuePredictor.cs | 5 +- .../Utilities/CommandLineUtilities.cs | 24 +++++- 5 files changed, 80 insertions(+), 76 deletions(-) diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index df4ece05e0b1..31faeb708341 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -50,7 +50,7 @@ public CommandLinePredictor(IList modelPredictions, ParameterValuePredic foreach (var predictionTextRaw in modelPredictions ?? Enumerable.Empty()) { - var predictionText = EscapePredictionText(predictionTextRaw); + var predictionText = CommandLineUtilities.EscapePredictionText(predictionTextRaw); Ast ast = Parser.ParseInput(predictionText, out Token[] tokens, out _); var commandAst = (ast.Find((ast) => ast is CommandAst, searchNestedScriptBlocks: false) as CommandAst); @@ -88,8 +88,9 @@ public CommandLineSuggestion GetSuggestion(string inputCommandName, Validation.CheckArgument(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0."); Validation.CheckArgument(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0."); + const int commandCollectionCapacity = 10; CommandLineSuggestion result = new(); - var resultsTemp = new Dictionary(StringComparer.OrdinalIgnoreCase); + var resultsTemp = new Dictionary(commandCollectionCapacity, StringComparer.OrdinalIgnoreCase); var isCommandNameComplete = inputParameterSet.Parameters.Any() || rawUserInput.EndsWith(' '); @@ -107,8 +108,9 @@ public CommandLineSuggestion GetSuggestion(string inputCommandName, // resultBuilder and usedParams are used to store the information to construct the result. // We want to avoid too much heap allocation for the performance purpose. + const int parameterCollectionCapacity = 10; var resultBuilder = new StringBuilder(); - var usedParams = new HashSet(); + var usedParams = new HashSet(parameterCollectionCapacity); var sourceBuilder = new StringBuilder(); for (var i = 0; i < _commandLinePredictions.Count && result.Count < suggestionCount; ++i) @@ -124,54 +126,45 @@ public CommandLineSuggestion GetSuggestion(string inputCommandName, if (DoesPredictionParameterSetMatchInput(resultBuilder, inputParameterSet, _commandLinePredictions[i].ParameterSet, usedParams)) { PredictRestOfParameters(resultBuilder, _commandLinePredictions[i].ParameterSet.Parameters, usedParams); - var prediction = UnescapePredictionText(resultBuilder); - if (prediction.Length <= rawUserInput.Length) + if (resultBuilder.Length <= rawUserInput.Length) { continue; } + var prediction = resultBuilder.ToString(); + sourceBuilder.Clear(); sourceBuilder.Append(_commandLinePredictions[i].Name); foreach (var p in _commandLinePredictions[i].ParameterSet.Parameters) { - _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = sourceBuilder.Append(p.Name); - - if (!string.IsNullOrWhiteSpace(p.Value)) - { - _ = sourceBuilder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = sourceBuilder.Append(p.Value); - } + AppendParameterNameAndValue(sourceBuilder, p.Name, p.Value); } if (!presentCommands.ContainsKey(_commandLinePredictions[i].Name)) { - result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); + result.AddSuggestion(new PredictiveSuggestion(prediction), sourceBuilder.ToString()); presentCommands.Add(_commandLinePredictions[i].Name, 1); } else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDuplicate) { - result.AddSuggestion(new PredictiveSuggestion(prediction.ToString()), sourceBuilder.ToString()); + result.AddSuggestion(new PredictiveSuggestion(prediction), sourceBuilder.ToString()); presentCommands[_commandLinePredictions[i].Name] += 1; } else { - _ = resultsTemp.TryAdd(prediction.ToString(), sourceBuilder.ToString()); - } - - if (result.Count == suggestionCount) - { - break; + _ = resultsTemp.TryAdd(prediction, sourceBuilder.ToString()); } } } } - if ((result.Count < suggestionCount) && (resultsTemp.Count > 0)) + var resultCount = result.Count; + + if ((resultCount < suggestionCount) && (resultsTemp.Count > 0)) { - foreach (var temp in resultsTemp.Take(suggestionCount - result.Count)) + foreach (var temp in resultsTemp.Take(suggestionCount - resultCount)) { result.AddSuggestion(new PredictiveSuggestion(temp.Key), temp.Value); } @@ -218,11 +211,7 @@ private bool DoesPredictionParameterSetMatchInput(StringBuilder builder, Paramet usedParams.Add(matchIndex); if (inputParameter.Value != null) { - _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = builder.Append(predictionParameters.Parameters[matchIndex].Name); - - _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = builder.Append(inputParameter.Value); + AppendParameterNameAndValue(builder, predictionParameters.Parameters[matchIndex].Name, inputParameter.Value); } else { @@ -230,6 +219,7 @@ private bool DoesPredictionParameterSetMatchInput(StringBuilder builder, Paramet } } } + return true; } @@ -248,21 +238,14 @@ private bool DoesPredictionParameterSetMatchInput(StringBuilder builder, Paramet private void BuildParameterValue(StringBuilder builder, Parameter parameter) { var parameterName = parameter.Name; - _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = builder.Append(parameterName); - - string parameterValue = this._parameterValuePredictor?.GetParameterValueFromAzCommand(parameterName); + var parameterValue = this._parameterValuePredictor?.GetParameterValueFromAzCommand(parameterName); if (string.IsNullOrWhiteSpace(parameterValue)) { parameterValue = parameter.Value; } - if (!string.IsNullOrWhiteSpace(parameterValue)) - { - _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); - _ = builder.Append(parameterValue); - } + AppendParameterNameAndValue(builder, parameterName, parameterValue); } /// @@ -286,19 +269,17 @@ private static int FindParameterPositionInSet(Parameter parameter, ParameterSet return -1; } - /// - /// Escaping the prediction text is necessary because KnowledgeBase predicted suggestions. - /// such as "<PSSubnetConfig>" are incorrectly identified as pipe operators. - /// - /// The text to escape. - private static string EscapePredictionText(string text) + private static void AppendParameterNameAndValue(StringBuilder builder, string name, string value) { - return text.Replace("<", "'<").Replace(">", ">'"); - } + _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); + _ = builder.Append(AzPredictorConstants.ParameterIndicator); + _ = builder.Append(name); - private static StringBuilder UnescapePredictionText(StringBuilder text) - { - return text.Replace("'<", "<").Replace(">'", ">"); + if (!string.IsNullOrWhiteSpace(value)) + { + _ = builder.Append(AzPredictorConstants.CommandParameterSeperator); + _ = builder.Append(value); + } } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLineSuggestion.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLineSuggestion.cs index 37afb23745b2..fcb3afc7b3a2 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLineSuggestion.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLineSuggestion.cs @@ -30,30 +30,36 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor /// public sealed class CommandLineSuggestion { - private readonly List _predictiveSuggestions = new(); + /// + /// Since PSReadLine can accept at most 10 suggestions, we pre-allocate that many items in the collection to avoid + /// re-allocation when we try to find the suggestion to return. + /// + private const int CollectionDefaultCapacity = 10; + + private readonly List _predictiveSuggestions = new List(CommandLineSuggestion.CollectionDefaultCapacity); /// /// Gets the suggestions returned to show to the user. This can be adjusted from based on /// the user input. /// - public IReadOnlyList PredictiveSuggestions { get { CheckObjectInvariant(); return _predictiveSuggestions; } } + public IReadOnlyList PredictiveSuggestions { get { return _predictiveSuggestions; } } - private readonly List _sourceTexts = new(); + private readonly List _sourceTexts = new List(CommandLineSuggestion.CollectionDefaultCapacity); /// /// Gets the texts that is based on. /// - public IReadOnlyList SourceTexts { get { CheckObjectInvariant(); return _sourceTexts; } } + public IReadOnlyList SourceTexts { get { return _sourceTexts; } } - private readonly List _suggestionSources = new(); + private readonly List _suggestionSources = new List(CommandLineSuggestion.CollectionDefaultCapacity); /// /// Gets or sets the sources where the text is from. /// - public IReadOnlyList SuggestionSources { get { CheckObjectInvariant(); return _suggestionSources; } } + public IReadOnlyList SuggestionSources { get { return _suggestionSources; } } /// /// Gets the number of suggestions. /// - public int Count { get { CheckObjectInvariant(); return _suggestionSources.Count; } } + public int Count { get { return _suggestionSources.Count; } } /// /// Adds a new suggestion. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs index 91204c7a5949..57f0f82c91e8 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterSet.cs @@ -37,17 +37,14 @@ public ParameterSet(CommandAst commandAst) var parameters = new List(); var elements = commandAst.CommandElements.Skip(1); - Ast param = null; + CommandParameterAst param = null; Ast arg = null; foreach (Ast elem in elements) { - if (elem is CommandParameterAst) + if (elem is CommandParameterAst p) { - if (param != null) - { - parameters.Add(new Parameter(param.ToString(), arg?.ToString())); - } - param = elem; + AddParameter(param, arg); + param = p; arg = null; } else if (AzPredictorConstants.ParameterIndicator == elem?.ToString().Trim().FirstOrDefault()) @@ -55,11 +52,7 @@ public ParameterSet(CommandAst commandAst) // We have an incomplete command line such as // `New-AzResourceGroup -Name ResourceGroup01 -Location WestUS -` // We'll ignore the incomplete parameter. - if (param != null) - { - parameters.Add(new Parameter(param.ToString(), arg?.ToString())); - } - + AddParameter(param, arg); param = null; arg = null; } @@ -71,12 +64,17 @@ public ParameterSet(CommandAst commandAst) Validation.CheckInvariant((param != null) || (arg == null)); - if (param != null) - { - parameters.Add(new Parameter(param.ToString(), arg?.ToString())); - } + AddParameter(param, arg); Parameters = parameters; + + void AddParameter(CommandParameterAst parameterName, Ast parameterValue) + { + if (parameterName != null) + { + parameters.Add(new Parameter(parameterName.ParameterName, (parameterValue == null) ? null : CommandLineUtilities.UnescapePredictionText(parameterValue.ToString()))); + } + } } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterValuePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterValuePredictor.cs index d42710a69205..703f85641cc1 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterValuePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/ParameterValuePredictor.cs @@ -42,7 +42,6 @@ public void ProcessHistoryCommand(CommandAst command) /// The parameter value from the history command. Null if that is not available. public string GetParameterValueFromAzCommand(string parameterName) { - parameterName = parameterName.TrimStart(AzPredictorConstants.ParameterIndicator); if (_localParameterValues.TryGetValue(parameterName.ToUpper(), out var value)) { return value; @@ -100,9 +99,9 @@ private void ExtractLocalParameters(System.Collections.ObjectModel.ReadOnlyColle for (int i = 2; i < command.Count; i += 2) { - if (command[i - 1] is CommandParameterAst && command[i] is StringConstantExpressionAst) + if (command[i - 1] is CommandParameterAst parameterAst && command[i] is StringConstantExpressionAst) { - var parameterName = command[i - 1].ToString().TrimStart(AzPredictorConstants.ParameterIndicator); + var parameterName = parameterAst.ParameterName; var key = ParameterValuePredictor.GetLocalParameterKey(commandNoun, parameterName); var parameterValue = command[i].ToString(); this._localParameterValues.AddOrUpdate(key, parameterValue, (k, v) => parameterValue); diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs index db9be23e6911..06ae716a0b47 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/CommandLineUtilities.cs @@ -57,7 +57,7 @@ public static string MaskCommandLine(CommandAst cmdAst) _ = sb.Append(AzPredictorConstants.CommandParameterSeperator); if (param.Argument != null) { - // Parameter is in the form of `-Name:name` + // Parameter is in the form of `-Name:value` _ = sb.Append(AzPredictorConstants.ParameterIndicator) .Append(param.ParameterName) .Append(AzPredictorConstants.ParameterValueSeperator) @@ -65,7 +65,7 @@ public static string MaskCommandLine(CommandAst cmdAst) } else { - // Parameter is in the form of `-Name` + // Parameter is in the form of `-Name value` _ = sb.Append(AzPredictorConstants.ParameterIndicator) .Append(param.ParameterName) .Append(AzPredictorConstants.CommandParameterSeperator) @@ -74,5 +74,25 @@ public static string MaskCommandLine(CommandAst cmdAst) } return sb.ToString(); } + + /// + /// Escaping the prediction text is necessary because KnowledgeBase predicted suggestions. + /// such as "<PSSubnetConfig>" are incorrectly identified as pipe operators. + /// + /// The text to escape. + public static string EscapePredictionText(string text) + { + return text.Replace("<", "'<").Replace(">", ">'"); + } + + /// + /// Unescape the prediction text from . + /// We don't want to show the escaped one to the user. + /// + /// The text to unescape. + public static string UnescapePredictionText(string text) + { + return text.Replace("'<", "<").Replace(">'", ">"); + } } } From a64b659fedd9959a506c9e146258eef951e35cb6 Mon Sep 17 00:00:00 2001 From: Maoliang Huang Date: Tue, 8 Dec 2020 13:37:41 -0800 Subject: [PATCH 15/15] Seperate the inner class in seperate files. --- .../Mocks/MockAzPredictorTelemetryClient.cs | 9 +- .../Az.Tools.Predictor/AzPredictor.cs | 7 +- .../Az.Tools.Predictor/AzPredictorService.cs | 9 +- .../CommandLinePredictor.cs | 4 +- .../AzPredictorTelemetryClient.cs | 38 ++-- .../Telemetry/GetSuggestionTelemetryData.cs | 69 +++++++ .../Telemetry/HistoryTelemetryData.cs | 39 ++++ .../{ => Telemetry}/ITelemetryClient.cs | 10 +- .../Telemetry/ITelemetryData.cs | 33 ++++ .../RequestPredictionTelemetryData.cs | 61 ++++++ .../SuggestionAcceptedTelemetryData.cs | 38 ++++ .../Az.Tools.Predictor/TelemetryData.cs | 179 ------------------ 12 files changed, 280 insertions(+), 216 deletions(-) rename tools/Az.Tools.Predictor/Az.Tools.Predictor/{ => Telemetry}/AzPredictorTelemetryClient.cs (86%) create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/GetSuggestionTelemetryData.cs create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/HistoryTelemetryData.cs rename tools/Az.Tools.Predictor/Az.Tools.Predictor/{ => Telemetry}/ITelemetryClient.cs (83%) create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryData.cs create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/RequestPredictionTelemetryData.cs create mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionAcceptedTelemetryData.cs delete mode 100644 tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs index ff68ee39b9d0..1728ac6ea0fc 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs @@ -13,6 +13,7 @@ // ---------------------------------------------------------------------------------- using System; +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry; namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test.Mocks { @@ -33,7 +34,7 @@ public class RecordedSuggestionForHistory public int SuggestionAccepted { get; set; } /// - public void OnHistory(TelemetryData.History telemetryData) + public void OnHistory(HistoryTelemetryData telemetryData) { this.RecordedSuggestion = new RecordedSuggestionForHistory() { @@ -42,18 +43,18 @@ public void OnHistory(TelemetryData.History telemetryData) } /// - public void OnRequestPrediction(TelemetryData.RequestPrediction telemetryData) + public void OnRequestPrediction(RequestPredictionTelemetryData telemetryData) { } /// - public void OnSuggestionAccepted(TelemetryData.SuggestionAccepted telemetryData) + public void OnSuggestionAccepted(SuggestionAcceptedTelemetryData telemetryData) { ++this.SuggestionAccepted; } /// - public void OnGetSuggestion(TelemetryData.GetSuggestion telemetryData) + public void OnGetSuggestion(GetSuggestionTelemetryData telemetryData) { } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs index 0a840ca4868c..a03eed3391a2 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry; using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.Collections.Generic; @@ -116,7 +117,7 @@ public void StartEarlyProcessing(IReadOnlyList history) _service.RecordHistory(lastCommand.Item1); } - _telemetryClient.OnHistory(new TelemetryData.History(lastCommand.Item2)); + _telemetryClient.OnHistory(new HistoryTelemetryData(lastCommand.Item2)); _service.RequestPredictions(_lastTwoMaskedCommands); } @@ -145,7 +146,7 @@ ValueTuple GetAstAndMaskedCommandLine(string commandLine) /// public void OnSuggestionAccepted(string acceptedSuggestion) { - _telemetryClient.OnSuggestionAccepted(new TelemetryData.SuggestionAccepted(acceptedSuggestion)); + _telemetryClient.OnSuggestionAccepted(new SuggestionAcceptedTelemetryData(acceptedSuggestion)); } /// @@ -176,7 +177,7 @@ public List GetSuggestion(PredictionContext context, Cance finally { - _telemetryClient.OnGetSuggestion(new TelemetryData.GetSuggestion(context.InputAst, + _telemetryClient.OnGetSuggestion(new GetSuggestionTelemetryData(context.InputAst, suggestions, cancellationToken.IsCancellationRequested, exception)); diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index c6524ad74c50..59605d701768 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry; using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.Collections.Generic; @@ -286,7 +287,7 @@ public virtual void RequestPredictions(IEnumerable commands) } finally { - _telemetryClient.OnRequestPrediction(new TelemetryData.RequestPrediction(localCommands, postSuccess, exception)); + _telemetryClient.OnRequestPrediction(new RequestPredictionTelemetryData(localCommands, postSuccess, exception)); } }, cancellationToken); @@ -300,7 +301,7 @@ public virtual void RequestPredictions(IEnumerable commands) { if (!startRequestTask) { - _telemetryClient.OnRequestPrediction(new TelemetryData.RequestPrediction(localCommands, hasSentHttpRequest: false, exception: exception)); + _telemetryClient.OnRequestPrediction(new RequestPredictionTelemetryData(localCommands, hasSentHttpRequest: false, exception: exception)); } } } @@ -317,7 +318,7 @@ public virtual void RecordHistory(CommandAst history) public bool IsSupportedCommand(string cmd) => !string.IsNullOrWhiteSpace(cmd) && (_allPredictiveCommands?.Contains(cmd) == true); /// - /// Requests a list of popular commands from service. These commands are used as fallback suggestion + /// Requests a list of popular commands from service. These commands are used as fall back suggestion /// if none of the predictions fit for the current input. This method should be called once per session. /// protected virtual void RequestAllPredictiveCommands() @@ -344,7 +345,7 @@ protected virtual void RequestAllPredictiveCommands() } finally { - _telemetryClient.OnRequestPrediction(new TelemetryData.RequestPrediction("request_commands", hasSentHttpRequest: true, exception: exception)); + _telemetryClient.OnRequestPrediction(new RequestPredictionTelemetryData("request_commands", hasSentHttpRequest: true, exception: exception)); } // Initialize predictions diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs index 31faeb708341..2a193f134ada 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLinePredictor.cs @@ -70,7 +70,7 @@ public CommandLinePredictor(IList modelPredictions, ParameterValuePredic /// The string format of the command line from user input. /// Commands already present. Contents may be added to this collection. /// The number of suggestions to return. - /// The maximum amount of the same commnds in the list of predictions. + /// The maximum amount of the same commands in the list of predictions. /// The cancellation token /// The collections of suggestions. public CommandLineSuggestion GetSuggestion(string inputCommandName, @@ -252,7 +252,7 @@ private void BuildParameterValue(StringBuilder builder, Parameter parameter) /// Determines the index of the given parameter in the parameter set. /// /// The parameter name and its value. - /// Prediction parameter setto find parameter position in. + /// Prediction parameter set to find parameter position in. /// Set of used parameters for set. private static int FindParameterPositionInSet(Parameter parameter, ParameterSet predictionSet, HashSet usedParams) { diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/AzPredictorTelemetryClient.cs similarity index 86% rename from tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs rename to tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/AzPredictorTelemetryClient.cs index d9df86c54cb1..52c1378600cc 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/AzPredictorTelemetryClient.cs @@ -24,7 +24,7 @@ using System.Text.Json; using System.Threading.Tasks.Dataflow; -namespace Microsoft.Azure.PowerShell.Tools.AzPredictor +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry { /// /// A telemetry client implementation to collect the telemetry data for AzPredictor. @@ -47,9 +47,9 @@ sealed class AzPredictorTelemetryClient : ITelemetryClient private readonly IAzContext _azContext; /// - /// The action to handle the in a thread pool. + /// The action to handle the in a thread pool. /// - private readonly ActionBlock _telemetryDispatcher; + private readonly ActionBlock _telemetryDispatcher; /// /// The adjusted texts and the source text for the suggestion. @@ -73,12 +73,12 @@ public AzPredictorTelemetryClient(IAzContext azContext) _telemetryClient.Context.Cloud.RoleInstance = "placeholderdon'tuse"; _telemetryClient.Context.Cloud.RoleName = "placeholderdon'tuse"; _azContext = azContext; - _telemetryDispatcher = new ActionBlock( + _telemetryDispatcher = new ActionBlock( (telemetryData) => DispatchTelemetryData(telemetryData)); } /// - public void OnHistory(TelemetryData.History telemetryData) + public void OnHistory(HistoryTelemetryData telemetryData) { if (!IsDataCollectionAllowed()) { @@ -96,7 +96,7 @@ public void OnHistory(TelemetryData.History telemetryData) } /// - public void OnRequestPrediction(TelemetryData.RequestPrediction telemetryData) + public void OnRequestPrediction(RequestPredictionTelemetryData telemetryData) { if (!IsDataCollectionAllowed()) { @@ -116,7 +116,7 @@ public void OnRequestPrediction(TelemetryData.RequestPrediction telemetryData) } /// - public void OnSuggestionAccepted(TelemetryData.SuggestionAccepted telemetryData) + public void OnSuggestionAccepted(SuggestionAcceptedTelemetryData telemetryData) { if (!IsDataCollectionAllowed()) { @@ -134,7 +134,7 @@ public void OnSuggestionAccepted(TelemetryData.SuggestionAccepted telemetryData) } /// - public void OnGetSuggestion(TelemetryData.GetSuggestion telemetryData) + public void OnGetSuggestion(GetSuggestionTelemetryData telemetryData) { if (!IsDataCollectionAllowed()) { @@ -166,22 +166,22 @@ private static bool IsDataCollectionAllowed() } /// - /// Dispatches according to its implementation. + /// Dispatches according to its implementation. /// - private void DispatchTelemetryData(TelemetryData.ITelemetryData telemetryData) + private void DispatchTelemetryData(ITelemetryData telemetryData) { switch (telemetryData) { - case TelemetryData.History history: + case HistoryTelemetryData history: SendTelemetry(history); break; - case TelemetryData.RequestPrediction requestPrediction: + case RequestPredictionTelemetryData requestPrediction: SendTelemetry(requestPrediction); break; - case TelemetryData.GetSuggestion getSuggestion: + case GetSuggestionTelemetryData getSuggestion: SendTelemetry(getSuggestion); break; - case TelemetryData.SuggestionAccepted suggestionAccepted: + case SuggestionAcceptedTelemetryData suggestionAccepted: SendTelemetry(suggestionAccepted); break; default: @@ -192,7 +192,7 @@ private void DispatchTelemetryData(TelemetryData.ITelemetryData telemetryData) /// /// Sends the telemetry with the command history. /// - private void SendTelemetry(TelemetryData.History telemetryData) + private void SendTelemetry(HistoryTelemetryData telemetryData) { var properties = CreateProperties(telemetryData); properties.Add("History", telemetryData.Command); @@ -203,7 +203,7 @@ private void SendTelemetry(TelemetryData.History telemetryData) /// /// Sends the telemetry with the commands for prediction. /// - private void SendTelemetry(TelemetryData.RequestPrediction telemetryData) + private void SendTelemetry(RequestPredictionTelemetryData telemetryData) { _userAcceptedAndSuggestion.Clear(); @@ -218,7 +218,7 @@ private void SendTelemetry(TelemetryData.RequestPrediction telemetryData) /// /// Sends the telemetry with the suggestion returned to the user. /// - private void SendTelemetry(TelemetryData.GetSuggestion telemetryData) + private void SendTelemetry(GetSuggestionTelemetryData telemetryData) { var suggestions = telemetryData.Suggestion?.PredictiveSuggestions; var suggestionSource = telemetryData.Suggestion?.SuggestionSources; @@ -245,7 +245,7 @@ private void SendTelemetry(TelemetryData.GetSuggestion telemetryData) /// /// Sends the telemetry with the suggestion returned to the user. /// - private void SendTelemetry(TelemetryData.SuggestionAccepted telemetryData) + private void SendTelemetry(SuggestionAcceptedTelemetryData telemetryData) { if (!_userAcceptedAndSuggestion.TryGetValue(telemetryData.Suggestion, out var suggestion)) { @@ -261,7 +261,7 @@ private void SendTelemetry(TelemetryData.SuggestionAccepted telemetryData) /// /// Add the common properties to the telemetry event. /// - private IDictionary CreateProperties(TelemetryData.ITelemetryData telemetryData) + private IDictionary CreateProperties(ITelemetryData telemetryData) { return new Dictionary() { diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/GetSuggestionTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/GetSuggestionTelemetryData.cs new file mode 100644 index 000000000000..9d2908c13690 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/GetSuggestionTelemetryData.cs @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Management.Automation.Language; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry +{ + /// + /// The data to collect in . + /// + public sealed class GetSuggestionTelemetryData : ITelemetryData + { + /// + public string SessionId { get; internal set; } + + /// + public string CorrelationId { get; internal set; } + + /// + /// Gets the user input. + /// + public Ast UserInput { get; } + + /// + /// Gets the suggestions to return to the user. + /// + public CommandLineSuggestion Suggestion { get; } + + /// + /// Gets whether the cancellation request is already set. + /// + public bool IsCancellationRequested { get; } + + /// + /// Gets the exception if there is an error during the operation. + /// + /// + /// OperationCanceledException isn't considered an error. + /// + public Exception Exception { get; } + + /// + /// Creates a new instance of . + /// + /// The user input that the is for. + /// The suggestions returned for the . + /// Indicates if the cancellation has been requested. + /// The exception that is thrown if there is an error. + public GetSuggestionTelemetryData(Ast userInput, CommandLineSuggestion suggestion, bool isCancellationRequested, Exception exception) + { + UserInput = userInput; + Suggestion = suggestion; + IsCancellationRequested = isCancellationRequested; + Exception = exception; + } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/HistoryTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/HistoryTelemetryData.cs new file mode 100644 index 000000000000..91b94209be40 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/HistoryTelemetryData.cs @@ -0,0 +1,39 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry +{ + /// + /// The data to collect in . + /// + public sealed class HistoryTelemetryData : ITelemetryData + { + /// + public string SessionId { get; internal set; } + + /// + public string CorrelationId { get; internal set; } + + /// + /// Gets the history command line. + /// + public string Command { get; } + + /// + /// Creates a new instance of . + /// + /// The history command line. + public HistoryTelemetryData(string command) => Command = command; + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryClient.cs similarity index 83% rename from tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs rename to tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryClient.cs index 70356928e2b9..037102beaa58 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/ITelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryClient.cs @@ -12,7 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace Microsoft.Azure.PowerShell.Tools.AzPredictor +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry { /// /// The telemetry client that collects and sends the telemetry data. @@ -33,24 +33,24 @@ public interface ITelemetryClient /// Collects the event of the history command. /// /// The data to collect. - public void OnHistory(TelemetryData.History telemetryData); + public void OnHistory(HistoryTelemetryData telemetryData); /// /// Collects the event when a prediction is requested. /// /// The data to collect. - public void OnRequestPrediction(TelemetryData.RequestPrediction telemetryData); + public void OnRequestPrediction(RequestPredictionTelemetryData telemetryData); /// /// Collects when a suggestion is accepted. /// /// The data to collect. - public void OnSuggestionAccepted(TelemetryData.SuggestionAccepted telemetryData); + public void OnSuggestionAccepted(SuggestionAcceptedTelemetryData telemetryData); /// /// Collects when we return a suggestion /// /// The data to collect. - public void OnGetSuggestion(TelemetryData.GetSuggestion telemetryData); + public void OnGetSuggestion(GetSuggestionTelemetryData telemetryData); } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryData.cs new file mode 100644 index 000000000000..a326a328cbe8 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryData.cs @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry +{ + /// + /// An interface that all telemetry data class should implement. + /// + public interface ITelemetryData + { + /// + /// Gets the session id. + /// + string SessionId { get; } + + /// + /// Gets the correlation id. + /// + string CorrelationId { get; } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/RequestPredictionTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/RequestPredictionTelemetryData.cs new file mode 100644 index 000000000000..dec60ac2f21c --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/RequestPredictionTelemetryData.cs @@ -0,0 +1,61 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry +{ + /// + /// The data to collect in . + /// + public sealed class RequestPredictionTelemetryData : ITelemetryData + { + /// + public string SessionId { get; internal set; } + + /// + public string CorrelationId { get; internal set; } + + /// + /// Gets the masked command lines that are used to request prediction. + /// + public string Commands { get; } // "Get-AzContext\nGet-AzVM" /predictions + + /// + /// Gets whether the http request to the service is sent. + /// + public bool HasSentHttpRequest { get; } + + /// + /// Gets the exception if there is an error during the operation. + /// + /// + /// OperationCanceledException isn't considered an error. + /// + public Exception Exception { get; } + + /// + /// Creates an instance of . + /// + /// The commands to request prediction for. + /// The flag to indicate whether the http request is canceled. + /// The exception that may be thrown. + public RequestPredictionTelemetryData(string commands, bool hasSentHttpRequest, Exception exception) + { + Commands = commands; + HasSentHttpRequest = hasSentHttpRequest; + Exception = exception; + } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionAcceptedTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionAcceptedTelemetryData.cs new file mode 100644 index 000000000000..133e97edfcbb --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionAcceptedTelemetryData.cs @@ -0,0 +1,38 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry +{ + /// + /// The data to collect in . + /// + public sealed class SuggestionAcceptedTelemetryData : ITelemetryData + { + /// + public string SessionId { get; internal set; } + + /// + public string CorrelationId { get; internal set; } + + /// + /// Gets the suggestion that's accepted by the user. + /// + public string Suggestion { get; } + + /// + /// Creates a new instance of . + /// + public SuggestionAcceptedTelemetryData(string suggestion) => Suggestion = suggestion; + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs deleted file mode 100644 index 5513be7f9fec..000000000000 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/TelemetryData.cs +++ /dev/null @@ -1,179 +0,0 @@ -// ---------------------------------------------------------------------------------- -// -// Copyright Microsoft Corporation -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ---------------------------------------------------------------------------------- - -using System; -using System.Management.Automation.Language; - -namespace Microsoft.Azure.PowerShell.Tools.AzPredictor -{ - /// - /// The collection of the data that're collected in telemetry. - /// - public static class TelemetryData - { - /// - /// An interface that all telemetry data class should implement. - /// - public interface ITelemetryData - { - /// - /// Gets the session id. - /// - string SessionId { get; } - - /// - /// Gets the correlation id. - /// - string CorrelationId { get; } - } - - /// - /// The data to collect in . - /// - public sealed class History : ITelemetryData - { - /// - public string SessionId { get; internal set; } - - /// - public string CorrelationId { get; internal set; } - - /// - /// Gets the history command line. - /// - public string Command { get; } - - /// - /// Creates a new instance of . - /// - /// The history command line. - public History(string command) => Command = command; - } - - /// - /// The data to collect in . - /// - public sealed class RequestPrediction : ITelemetryData - { - /// - public string SessionId { get; internal set; } - - /// - public string CorrelationId { get; internal set; } - - /// - /// Gets the masked command lines that are used to request prediction. - /// - public string Commands { get; } - - /// - /// Gets whether the http request to the service is sent. - /// - public bool HasSentHttpRequest { get; } - - /// - /// Gets the exception if there is an error during the operation. - /// - /// - /// OperationCanceledException isn't considered an error. - /// - public Exception Exception { get; } - - /// - /// Creates an instance of . - /// - /// The commands to request prediction for. - /// The flag to indicate whether the http request is canceled. - /// The exception that may be thrown. - public RequestPrediction(string commands, bool hasSentHttpRequest, Exception exception) - { - Commands = commands; - HasSentHttpRequest = hasSentHttpRequest; - Exception = exception; - } - } - - /// - /// The data to collect in . - /// - public sealed class GetSuggestion : ITelemetryData - { - /// - public string SessionId { get; internal set; } - - /// - public string CorrelationId { get; internal set; } - - /// - /// Gets the user input. - /// - public Ast UserInput { get; } - - /// - /// Gets the suggestions to return to the user. - /// - public CommandLineSuggestion Suggestion { get; } - - /// - /// Gets whether the cancellation request is already set. - /// - public bool IsCancellationRequested { get; } - - /// - /// Gets the exception if there is an error during the operation. - /// - /// - /// OperationCanceledException isn't considered an error. - /// - public Exception Exception { get; } - - /// - /// Creates a new instance of . - /// - /// The user input that the is for. - /// The suggestions returned for the . - /// Indicates if the cancellation has been requested. - /// The exception that is thrown if there is an error. - public GetSuggestion(Ast userInput, CommandLineSuggestion suggestion, bool isCancellationRequested, Exception exception) - { - UserInput = userInput; - Suggestion = suggestion; - IsCancellationRequested = isCancellationRequested; - Exception = exception; - } - } - - /// - /// The data to collect in . - /// - public sealed class SuggestionAccepted : ITelemetryData - { - /// - public string SessionId { get; internal set; } - - /// - public string CorrelationId { get; internal set; } - - /// - /// Gets the suggestion that's accepted by the user. - /// - public string Suggestion { get; } - - /// - /// Creates a new instance of . - /// - public SuggestionAccepted(string suggestion) => Suggestion = suggestion; - } - } -}