Skip to content

Commit

Permalink
Added Cmdlets to help with troubleshooting
Browse files Browse the repository at this point in the history
Resolves: #26
  • Loading branch information
AnderssonPeter committed Jan 10, 2022
1 parent 2158e15 commit d663361
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 38 deletions.
3 changes: 0 additions & 3 deletions PowerType/BackgroundProcessing/ExecutionEngineThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,6 @@ private void Handle(InitializeDictionaryCommand command)
throw new InvalidOperationException("Didn't receive a PowerTypeDictionary or ISuggestor");
}



lock (dictionariesLocker)
{
dictionaries.Add(new DictionaryInformation(command.File, runspace, suggestor));
Expand Down Expand Up @@ -183,7 +181,6 @@ protected virtual void Dispose(bool disposing)

if (disposing)
{

cancellationTokenSource.Cancel();
//While we wait for the background thread to exit lets do some cleanup

Expand Down
14 changes: 6 additions & 8 deletions PowerType/EnablePowerTypePredictor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@ namespace PowerType;
[Cmdlet("Enable", "PowerType"), OutputType(typeof(bool))]
public class EnablePowerTypePredictor : PowerTypeCmdlet
{
private static readonly string enableStatement = "Set-PSReadLineOption -PredictionSource Plugin";

private const string enableStatement = "Set-PSReadLineOption -PredictionSource Plugin";

/// <summary>
/// <para type="description">Indicates whether the user would like to receive output. </para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Indicates whether the user would like to receive output.")]
public SwitchParameter PassThru { get; set; }


/// <summary>
/// <para type="description">Lazy load prediction dictionaries. </para>
/// </summary>
Expand All @@ -46,19 +44,19 @@ public class EnablePowerTypePredictor : PowerTypeCmdlet
[Parameter(Mandatory = false, HelpMessage = "List of dictionaries to load, if not provided all are loaded.")]
public string[]? DictionariesToIgnore { get; set; }


/// <inheritdoc/>
protected override void ProcessRecord()
{
var scriptToRun = new StringBuilder();
var _ = scriptToRun.Append(EnablePowerTypePredictor.enableStatement);
scriptToRun.Append(enableStatement);

InvokeCommand.InvokeScript(scriptToRun.ToString());

var currentWorkingDirectoryProvider = new CurrentWorkingDirectoryProvider(SessionState);
var predictor = new PowerTypePredictor(currentWorkingDirectoryProvider, GetDictionaries());
SubsystemManager.RegisterSubsystem<ICommandPredictor, PowerTypePredictor>(predictor);

PowerTypePredictor.Instance = predictor;

if (PassThru.IsPresent)
{
WriteObject(true);
Expand All @@ -70,8 +68,8 @@ public static string AssemblyPath
get
{
string codeBase = Assembly.GetExecutingAssembly().Location;
UriBuilder uri = new UriBuilder(codeBase);
return Uri.UnescapeDataString(uri.Path);;
UriBuilder uri = new(codeBase);
return Uri.UnescapeDataString(uri.Path);
}
}

Expand Down
23 changes: 23 additions & 0 deletions PowerType/GetPowerTypeHistory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Management.Automation;

namespace PowerType;

/// <summary>
/// <para type="synopsis">Cmdlet to disable PowerType Predictor and start receiving suggestions</para>
/// <para type="description">Use this cmdlet to disable PowerType Predictor and start receiving suggestions</para>
/// </summary>
[Cmdlet("Get", "PowerTypeHistory"), OutputType(typeof(bool))]
public class GetPowerTypeHistory : PowerTypeCmdlet
{
/// <inheritdoc/>
protected override void ProcessRecord()
{
var instance = PowerTypePredictor.Instance;
if (instance == null)
{
throw new InvalidOperationException("Please call 'Enable-PowerTypePredictor' first");
}
this.WriteObject(instance.PredictionSummaryCollector.Get());
}
}

26 changes: 26 additions & 0 deletions PowerType/GetPowerTypeStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Management.Automation;
using System.Management.Automation.Subsystem;

namespace PowerType;

public record PowerTypeStatus(bool ExecutionEngineIsRunning, Exception? ExecutionEngineException);

/// <summary>
/// <para type="synopsis">Cmdlet to disable PowerType Predictor and start receiving suggestions</para>
/// <para type="description">Use this cmdlet to disable PowerType Predictor and start receiving suggestions</para>
/// </summary>
[Cmdlet("Get", "PowerTypeStatus"), OutputType(typeof(bool))]
public class GetPowerTypeStatus : PowerTypeCmdlet
{

/// <inheritdoc/>
protected override void ProcessRecord()
{
var instance = PowerTypePredictor.Instance;
if (instance == null)
{
throw new InvalidOperationException("Please call 'Enable-PowerTypePredictor' first");
}
this.WriteObject(new PowerTypeStatus(instance.ExecutionEngine.IsHealthy(out Exception? exception), exception));
}
}
4 changes: 3 additions & 1 deletion PowerType/PowerType.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Description = 'Module providing recommendations for common tools - This module r
Suggestions must be activated:
- Enable-PowerType: Activate the suggestions
- Disable-PowerType: Disable the suggestions
- Get-PowerTypeStatus Gets the status of PowerType, this is useful for troubleshooting issues
- Get-PowerTypeHistory Gets the prediction history of PowerType, this is useful for troubleshooting issues
For more information on PowerType, please visit the following: https://github.com/AnderssonPeter/PowerType'

Expand All @@ -45,7 +47,7 @@ PowerShellVersion = '7.2'

NestedModules = @("PowerType.dll")

CmdletsToExport = @("Enable-PowerType", "Disable-PowerType")
CmdletsToExport = @("Enable-PowerType", "Disable-PowerType", "Get-PowerTypeStatus", "Get-PowerTypeHistory")

# Format files (.ps1xml) to be loaded when importing this module

Expand Down
1 change: 0 additions & 1 deletion PowerType/PowerTypeCmdlet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ namespace PowerType;

public abstract class PowerTypeCmdlet : PSCmdlet
{

#if DEBUG
/// <inheritdoc/>
protected override void BeginProcessing()
Expand Down
38 changes: 38 additions & 0 deletions PowerType/PowerTypePredictionSummaryCollector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Diagnostics;
using System.Management.Automation.Subsystem.Prediction;

namespace PowerType;

public record struct PowerTypePredictionSummary(DateTime When, string Input, string? TokenAtCursor, Exception? Exception, string[] Suggestions, TimeSpan Duration);

public class PowerTypePredictionSummaryCollector
{
private readonly object locker = new ();
private readonly PowerTypePredictionSummary[] items;
private int offset = 0;

public PowerTypePredictionSummaryCollector(int size = 20)
{
items = new PowerTypePredictionSummary[size];
}

public void Add(DateTime when, PredictionContext predictionContext, Exception? exception, SuggestionPackage suggestionPackage, TimeSpan duration)
{
lock (locker)
{
var index = offset++ % items.Length;
items[index] = new PowerTypePredictionSummary(when, predictionContext.InputAst.ToString(), predictionContext.TokenAtCursor?.ToString(), exception, suggestionPackage.SuggestionEntries.Select(x => x.SuggestionText).ToArray(), duration);
}
}

public List<PowerTypePredictionSummary> Get()
{
lock (locker)
{
return items.Take(Math.Min(offset, items.Length))
.OrderBy(x => x.When)
.ToList();
}
}
}

61 changes: 36 additions & 25 deletions PowerType/PowerTypePredictor.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
using PowerType.BackgroundProcessing;
using PowerType.Parsing;
using System.Diagnostics;
using System.Management.Automation.Language;
using System.Management.Automation.Subsystem.Prediction;

namespace PowerType;

public sealed class PowerTypePredictor : ICommandPredictor, IDisposable
{
private readonly ExecutionEngine executionEngine;
//Ugly hack to allow cmdlets access the instance after it has been created
public static volatile PowerTypePredictor? Instance;

internal ExecutionEngine ExecutionEngine { get; }
internal PowerTypePredictionSummaryCollector PredictionSummaryCollector { get; }
public string Description => "Provides suggestions for common command tools";

public Guid Id => Identifier;
Expand All @@ -19,11 +24,12 @@ public sealed class PowerTypePredictor : ICommandPredictor, IDisposable

public PowerTypePredictor(ICurrentWorkingDirectoryProvider currentWorkingDirectoryProvider, IEnumerable<string> dictionaryFiles)
{
executionEngine = new ExecutionEngine();
executionEngine.Start();
foreach(var dictionaryFile in dictionaryFiles)
ExecutionEngine = new ExecutionEngine();
ExecutionEngine.Start();
PredictionSummaryCollector = new PowerTypePredictionSummaryCollector();
foreach (var dictionaryFile in dictionaryFiles)
{
executionEngine.InitialDictionary(dictionaryFile);
ExecutionEngine.InitialDictionary(dictionaryFile);
}

this.currentWorkingDirectoryProvider = currentWorkingDirectoryProvider;
Expand All @@ -42,6 +48,10 @@ private static string ConstructCommandPrefix(CommandAst commandAst)

public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContext context, CancellationToken cancellationToken)
{
var when = DateTime.Now;
var watch = Stopwatch.StartNew();
Exception? exception = null;
SuggestionPackage suggestionPackage = default;
try
{
var relatedAsts = context.RelatedAsts;
Expand All @@ -56,28 +66,29 @@ public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContex
}
}

if (commandAst == null)
{
return new SuggestionPackage();
}

var commandName = commandAst.GetCommandName();
var arguments = commandAst.CommandElements.Select(x => new PowerShellString(x));
var prefix = ConstructCommandPrefix(commandAst);
var dictionaryParsingContext = new DictionaryParsingContext(prefix, arguments);
var result = GetSuggestions(dictionaryParsingContext).ToList();
if (result.Count == 0)
if (commandAst != null)
{
return default;
var commandName = commandAst.GetCommandName();
var arguments = commandAst.CommandElements.Select(x => new PowerShellString(x));
var prefix = ConstructCommandPrefix(commandAst);
var dictionaryParsingContext = new DictionaryParsingContext(prefix, arguments);
var result = GetSuggestions(dictionaryParsingContext).ToList();
if (result.Count > 0)
{
suggestionPackage = new SuggestionPackage(result);
}
}
return new SuggestionPackage(result);
}
catch (Exception ex)
{
Console.WriteLine("input: {0}", context.InputAst);
Console.WriteLine("exception: {0}", ex);
return default;
exception = ex;
}
watch.Stop();
if (exception != null || suggestionPackage.SuggestionEntries != null)
{
this.PredictionSummaryCollector.Add(when, context, exception, suggestionPackage, watch.Elapsed);
}
return suggestionPackage;
}

private IEnumerable<PredictiveSuggestion> GetSuggestions(DictionaryParsingContext dictionaryParsingContext)
Expand All @@ -97,7 +108,7 @@ private IEnumerable<PredictiveSuggestion> GetSuggestions(DictionaryParsingContex
if (TryGetSuggestor(commandName, out var key, out var suggestor))
{
dictionaryParsingContext.Command = new Parsing.Command(key, suggestor.Dictionary);
executionEngine.Cache(suggestor.Dictionary, currentWorkingDirectoryProvider.CurrentWorkingDirectory);
ExecutionEngine.Cache(suggestor.Dictionary, currentWorkingDirectoryProvider.CurrentWorkingDirectory);
foreach (var result in suggestor.GetPredictions(dictionaryParsingContext))
{
yield return result;
Expand All @@ -107,7 +118,7 @@ private IEnumerable<PredictiveSuggestion> GetSuggestions(DictionaryParsingContex

private bool TryGetSuggestor(PowerShellString command, out string key, out DictionarySuggestor suggestor)
{
var suggestors = executionEngine.GetSuggestors();
var suggestors = ExecutionEngine.GetSuggestors();
foreach (var innerSuggestor in suggestors)
{
foreach (var suggestorKey in innerSuggestor.Keys)
Expand All @@ -127,7 +138,7 @@ private bool TryGetSuggestor(PowerShellString command, out string key, out Dicti

private IEnumerable<PredictiveSuggestion> GetDictrionaryPredictons(PowerShellString commandName)
{
var suggestors = executionEngine.GetSuggestors();
var suggestors = ExecutionEngine.GetSuggestors();
foreach (var suggestor in suggestors)
{
var key = suggestor.Keys.FirstOrDefault(x => x.Contains(commandName.RawValue, StringComparison.OrdinalIgnoreCase));
Expand Down Expand Up @@ -160,7 +171,7 @@ public void OnSuggestionDisplayed(PredictionClient client, uint session, int cou

public void Dispose()
{
executionEngine.Stop();
ExecutionEngine.Stop();
currentWorkingDirectoryProvider.Dispose();
}
}

0 comments on commit d663361

Please sign in to comment.