//
// Copyright (c) 2012 Microsoft Corporation. All rights reserved.
//
// DISCLAIMER OF WARRANTY: The software is licensed “as-is.” You
// bear the risk of using it. Microsoft gives no express warranties,
// guarantees or conditions. You may have additional consumer rights
// under your local laws which this agreement cannot change. To the extent
// permitted under your local laws, Microsoft excludes the implied warranties
// of merchantability, fitness for a particular purpose and non-infringement.
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
namespace Microsoft.Windows.PowerShell
{
///
/// Cmdlet to run profiler on provided scripts.
/// If Ast switch is set then the Ast object is returned,
/// otherwise time elapsed per script line is displayed.
///
[Cmdlet("Measure", "Script")]
public sealed class MeasureScriptCommand : PSCmdlet
{
[Parameter(Position=0, ValueFromPipeline=true)]
public string[] Path { get; set; }
[Parameter]
public SwitchParameter Ast { get; set; }
protected override void ProcessRecord()
{
foreach (var path in Path)
{
ProviderInfo provider;
var resolvedPath = SessionState.Path.GetResolvedProviderPathFromPSPath(path, out provider).FirstOrDefault();
if (resolvedPath != null)
{
Profile(resolvedPath);
}
else
{
WriteError(new ErrorRecord(new ArgumentException(path), "ProfilerCantResolvePath", ErrorCategory.InvalidArgument, path));
}
}
}
///
/// Method to read the provided script file and either write
/// the created instrumented AST object or write elapsed time
/// for each line in script.
///
///
private void Profile(string file)
{
Token[] tokens;
ParseError[] errors;
var ast = Parser.ParseFile(file, out tokens, out errors);
Profiler profiler = new Profiler(ast.Extent);
var instrumentor = new InstrumentAst { Profiler = profiler };
ScriptBlockAst newAst = (ScriptBlockAst)ast.Visit(instrumentor);
if (Ast)
{
WriteObject(newAst);
return;
}
ScriptBlock sb = newAst.GetScriptBlock();
sb.Invoke();
string[] allLines = File.ReadAllLines(file);
for (int i = 0; i < allLines.Length; ++i)
{
long time = (i >= profiler.StopWatches.Length) ? 0 : profiler.StopWatches[i].ElapsedMilliseconds;
var result = new PSObject();
result.Members.Add(new PSNoteProperty("Time", time));
result.Members.Add(new PSNoteProperty("Line", allLines[i]));
WriteObject(result);
}
}
}
///
/// Profiler class manages StopWatch objects for each
/// script line.
///
public sealed class Profiler
{
internal Stopwatch[] StopWatches { get; set; }
internal Profiler(IScriptExtent extent)
{
int lines = extent.EndLineNumber;
StopWatches = new Stopwatch[lines];
for (int i = 0; i < lines; ++i)
{
StopWatches[i] = new Stopwatch();
}
}
public void StartLine(int line)
{
StopWatches[line].Start();
}
public void EndLine(int line)
{
StopWatches[line].Stop();
}
}
///
/// Instrumented Ast class. Builds an Ast that makes callbacks
/// into Profiler for measuring statement execution times.
/// This implementation is needed to add StartLine and StopLine
/// profiler object call backs that will record the execution
/// time for each script statement. A complete ICustomAstVistor
/// implementation must be created and so all methods except for
/// "VisitStatements" are boiler plate implementations for the
/// ICustomAstVistor interface.
///
public sealed class InstrumentAst : ICustomAstVisitor
{
internal Profiler Profiler { get; set; }
public object VisitScriptBlock(ScriptBlockAst scriptBlockAst)
{
var newParamBlock = VisitElement(scriptBlockAst.ParamBlock);
var newBeginBlock = VisitElement(scriptBlockAst.BeginBlock);
var newProcessBlock = VisitElement(scriptBlockAst.ProcessBlock);
var newEndBlock = VisitElement(scriptBlockAst.EndBlock);
var newDynamicParamBlock = VisitElement(scriptBlockAst.DynamicParamBlock);
return new ScriptBlockAst(scriptBlockAst.Extent, newParamBlock, newBeginBlock, newProcessBlock, newEndBlock,
newDynamicParamBlock);
}
public T[] VisitElements(ReadOnlyCollection elements) where T : Ast
{
if (elements == null)
{
return new T[0];
}
var newElements = new List();
foreach (T t in elements)
{
newElements.Add((T)t.Visit(this));
}
return newElements.ToArray();
}
public T VisitElement(T element) where T : Ast
{
if (element == null)
return null;
return (T)element.Visit(this);
}
///
/// Inserts StartLine and EndLine Profiler callbacks for each
/// script statement.
///
///
///
public StatementAst[] VisitStatements(ReadOnlyCollection statements)
{
var newStatements = new List();
foreach (var statement in statements)
{
bool instrument = (statement is PipelineBaseAst);
IScriptExtent extent = statement.Extent;
if (instrument)
{
var startLine = new CommandExpressionAst(
extent, new InvokeMemberExpressionAst(extent,
new ConstantExpressionAst(extent, Profiler),
new StringConstantExpressionAst(extent, "StartLine", StringConstantType.BareWord),
new [] { new ConstantExpressionAst(extent, extent.StartLineNumber - 1) }, false), null);
var pipe = new PipelineAst(extent, startLine);
newStatements.Add(pipe);
}
newStatements.Add(VisitElement(statement));
if (instrument)
{
var endLine = new CommandExpressionAst(
extent, new InvokeMemberExpressionAst(extent,
new ConstantExpressionAst(extent, Profiler),
new StringConstantExpressionAst(extent, "EndLine", StringConstantType.BareWord),
new[] { new ConstantExpressionAst(extent, extent.StartLineNumber - 1) }, false), null);
var pipe = new PipelineAst(extent, endLine);
newStatements.Add(pipe);
}
}
return newStatements.ToArray();
}
public object VisitNamedBlock(NamedBlockAst namedBlockAst)
{
var newTraps = VisitElements(namedBlockAst.Traps);
var newStatements = VisitStatements(namedBlockAst.Statements);
var statementBlock = new StatementBlockAst(namedBlockAst.Extent, newStatements, newTraps);
return new NamedBlockAst(namedBlockAst.Extent, namedBlockAst.BlockKind, statementBlock, namedBlockAst.Unnamed);
}
public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
{
var newBody = VisitElement(functionDefinitionAst.Body);
return new FunctionDefinitionAst(functionDefinitionAst.Extent, functionDefinitionAst.IsFilter,
functionDefinitionAst.IsWorkflow, functionDefinitionAst.Name,
VisitElements(functionDefinitionAst.Parameters), newBody);
}
public object VisitStatementBlock(StatementBlockAst statementBlockAst)
{
var newStatements = VisitStatements(statementBlockAst.Statements);
var newTraps = VisitElements(statementBlockAst.Traps);
return new StatementBlockAst(statementBlockAst.Extent, newStatements, newTraps);
}
public object VisitIfStatement(IfStatementAst ifStmtAst)
{
var newClauses = (from clause in ifStmtAst.Clauses
let newClauseTest = VisitElement(clause.Item1)
let newStatementBlock = VisitElement(clause.Item2)
select new Tuple(newClauseTest, newStatementBlock));
var newElseClause = VisitElement(ifStmtAst.ElseClause);
return new IfStatementAst(ifStmtAst.Extent, newClauses, newElseClause);
}
public object VisitTrap(TrapStatementAst trapStatementAst)
{
return new TrapStatementAst(trapStatementAst.Extent, VisitElement(trapStatementAst.TrapType), VisitElement(trapStatementAst.Body));
}
public object VisitSwitchStatement(SwitchStatementAst switchStatementAst)
{
var newCondition = VisitElement(switchStatementAst.Condition);
var newClauses = (from clause in switchStatementAst.Clauses
let newClauseTest = VisitElement(clause.Item1)
let newStatementBlock = VisitElement(clause.Item2)
select new Tuple(newClauseTest, newStatementBlock));
var newDefault = VisitElement(switchStatementAst.Default);
return new SwitchStatementAst(switchStatementAst.Extent, switchStatementAst.Label,
newCondition,
switchStatementAst.Flags, newClauses, newDefault);
}
public object VisitDataStatement(DataStatementAst dataStatementAst)
{
var newBody = VisitElement(dataStatementAst.Body);
var newCommandsAllowed = VisitElements(dataStatementAst.CommandsAllowed);
return new DataStatementAst(dataStatementAst.Extent, dataStatementAst.Variable, newCommandsAllowed, newBody);
}
public object VisitForEachStatement(ForEachStatementAst forEachStatementAst)
{
var newVariable = VisitElement(forEachStatementAst.Variable);
var newCondition = VisitElement(forEachStatementAst.Condition);
var newBody = VisitElement(forEachStatementAst.Body);
return new ForEachStatementAst(forEachStatementAst.Extent, forEachStatementAst.Label, ForEachFlags.None,
newVariable, newCondition, newBody);
}
public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst)
{
var newCondition = VisitElement(doWhileStatementAst.Condition);
var newBody = VisitElement(doWhileStatementAst.Body);
return new DoWhileStatementAst(doWhileStatementAst.Extent, doWhileStatementAst.Label, newCondition, newBody);
}
public object VisitForStatement(ForStatementAst forStatementAst)
{
var newInitializer = VisitElement(forStatementAst.Initializer);
var newCondition = VisitElement(forStatementAst.Condition);
var newIterator = VisitElement(forStatementAst.Iterator);
var newBody = VisitElement(forStatementAst.Body);
return new ForStatementAst(forStatementAst.Extent, forStatementAst.Label, newInitializer,
newCondition, newIterator, newBody);
}
public object VisitWhileStatement(WhileStatementAst whileStatementAst)
{
var newCondition = VisitElement(whileStatementAst.Condition);
var newBody = VisitElement(whileStatementAst.Body);
return new WhileStatementAst(whileStatementAst.Extent, whileStatementAst.Label, newCondition, newBody);
}
public object VisitCatchClause(CatchClauseAst catchClauseAst)
{
var newBody = VisitElement(catchClauseAst.Body);
return new CatchClauseAst(catchClauseAst.Extent, catchClauseAst.CatchTypes, newBody);
}
public object VisitTryStatement(TryStatementAst tryStatementAst)
{
var newBody = VisitElement(tryStatementAst.Body);
var newCatchClauses = VisitElements(tryStatementAst.CatchClauses);
var newFinally = VisitElement(tryStatementAst.Finally);
return new TryStatementAst(tryStatementAst.Extent, newBody, newCatchClauses, newFinally);
}
public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst)
{
var newCondition = VisitElement(doUntilStatementAst.Condition);
var newBody = VisitElement(doUntilStatementAst.Body);
return new DoUntilStatementAst(doUntilStatementAst.Extent, doUntilStatementAst.Label,
newCondition, newBody);
}
public object VisitParamBlock(ParamBlockAst paramBlockAst)
{
var newAttributes = VisitElements(paramBlockAst.Attributes);
var newParameters = VisitElements(paramBlockAst.Parameters);
return new ParamBlockAst(paramBlockAst.Extent, newAttributes, newParameters);
}
public object VisitErrorStatement(ErrorStatementAst errorStatementAst)
{
return errorStatementAst;
}
public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst)
{
return errorExpressionAst;
}
public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst)
{
return new TypeConstraintAst(typeConstraintAst.Extent, typeConstraintAst.TypeName);
}
public object VisitAttribute(AttributeAst attributeAst)
{
var newPositionalArguments = VisitElements(attributeAst.PositionalArguments);
var newNamedArguments = VisitElements(attributeAst.NamedArguments);
return new AttributeAst(attributeAst.Extent, attributeAst.TypeName, newPositionalArguments, newNamedArguments);
}
public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst)
{
var newArgument = VisitElement(namedAttributeArgumentAst.Argument);
return new NamedAttributeArgumentAst(namedAttributeArgumentAst.Extent,
namedAttributeArgumentAst.ArgumentName, newArgument,
namedAttributeArgumentAst.ExpressionOmitted);
}
public object VisitParameter(ParameterAst parameterAst)
{
var newName = VisitElement(parameterAst.Name);
var newAttributes = VisitElements(parameterAst.Attributes);
var newDefaultValue = VisitElement(parameterAst.DefaultValue);
return new ParameterAst(parameterAst.Extent, newName, newAttributes, newDefaultValue);
}
public object VisitBreakStatement(BreakStatementAst breakStatementAst)
{
var newLabel = VisitElement(breakStatementAst.Label);
return new BreakStatementAst(breakStatementAst.Extent, newLabel);
}
public object VisitContinueStatement(ContinueStatementAst continueStatementAst)
{
var newLabel = VisitElement(continueStatementAst.Label);
return new ContinueStatementAst(continueStatementAst.Extent, newLabel);
}
public object VisitReturnStatement(ReturnStatementAst returnStatementAst)
{
var newPipeline = VisitElement(returnStatementAst.Pipeline);
return new ReturnStatementAst(returnStatementAst.Extent, newPipeline);
}
public object VisitExitStatement(ExitStatementAst exitStatementAst)
{
var newPipeline = VisitElement(exitStatementAst.Pipeline);
return new ExitStatementAst(exitStatementAst.Extent, newPipeline);
}
public object VisitThrowStatement(ThrowStatementAst throwStatementAst)
{
var newPipeline = VisitElement(throwStatementAst.Pipeline);
return new ThrowStatementAst(throwStatementAst.Extent, newPipeline);
}
public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst)
{
var newLeft = VisitElement(assignmentStatementAst.Left);
var newRight = VisitElement(assignmentStatementAst.Right);
return new AssignmentStatementAst(assignmentStatementAst.Extent, newLeft, assignmentStatementAst.Operator,
newRight, assignmentStatementAst.ErrorPosition);
}
public object VisitPipeline(PipelineAst pipelineAst)
{
var newPipeElements = VisitElements(pipelineAst.PipelineElements);
return new PipelineAst(pipelineAst.Extent, newPipeElements);
}
public object VisitCommand(CommandAst commandAst)
{
var newCommandElements = VisitElements(commandAst.CommandElements);
var newRedirections = VisitElements(commandAst.Redirections);
return new CommandAst(commandAst.Extent, newCommandElements, commandAst.InvocationOperator, newRedirections);
}
public object VisitCommandExpression(CommandExpressionAst commandExpressionAst)
{
var newExpression = VisitElement(commandExpressionAst.Expression);
var newRedirections = VisitElements(commandExpressionAst.Redirections);
return new CommandExpressionAst(commandExpressionAst.Extent, newExpression, newRedirections);
}
public object VisitCommandParameter(CommandParameterAst commandParameterAst)
{
var newArgument = VisitElement(commandParameterAst.Argument);
return new CommandParameterAst(commandParameterAst.Extent, commandParameterAst.ParameterName, newArgument,
commandParameterAst.ErrorPosition);
}
public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst)
{
var newFile = VisitElement(fileRedirectionAst.Location);
return new FileRedirectionAst(fileRedirectionAst.Extent, fileRedirectionAst.FromStream, newFile,
fileRedirectionAst.Append);
}
public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst)
{
return new MergingRedirectionAst(mergingRedirectionAst.Extent, mergingRedirectionAst.FromStream,
mergingRedirectionAst.ToStream);
}
public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
{
var newLeft = VisitElement(binaryExpressionAst.Left);
var newRight = VisitElement(binaryExpressionAst.Right);
return new BinaryExpressionAst(binaryExpressionAst.Extent, newLeft, binaryExpressionAst.Operator, newRight,
binaryExpressionAst.ErrorPosition);
}
public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst)
{
var newChild = VisitElement(unaryExpressionAst.Child);
return new UnaryExpressionAst(unaryExpressionAst.Extent, unaryExpressionAst.TokenKind, newChild);
}
public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst)
{
var newChild = VisitElement(convertExpressionAst.Child);
var newTypeConstraint = VisitElement(convertExpressionAst.Type);
return new ConvertExpressionAst(convertExpressionAst.Extent, newTypeConstraint, newChild);
}
public object VisitTypeExpression(TypeExpressionAst typeExpressionAst)
{
return new TypeExpressionAst(typeExpressionAst.Extent, typeExpressionAst.TypeName);
}
public object VisitConstantExpression(ConstantExpressionAst constantExpressionAst)
{
return new ConstantExpressionAst(constantExpressionAst.Extent, constantExpressionAst.Value);
}
public object VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst)
{
return new StringConstantExpressionAst(stringConstantExpressionAst.Extent, stringConstantExpressionAst.Value,
stringConstantExpressionAst.StringConstantType);
}
public object VisitSubExpression(SubExpressionAst subExpressionAst)
{
var newStatementBlock = VisitElement(subExpressionAst.SubExpression);
return new SubExpressionAst(subExpressionAst.Extent, newStatementBlock);
}
public object VisitUsingExpression(UsingExpressionAst usingExpressionAst)
{
var newUsingExpr = VisitElement(usingExpressionAst.SubExpression);
return new UsingExpressionAst(usingExpressionAst.Extent, newUsingExpr);
}
public object VisitVariableExpression(VariableExpressionAst variableExpressionAst)
{
return new VariableExpressionAst(variableExpressionAst.Extent, variableExpressionAst.VariablePath.UserPath,
variableExpressionAst.Splatted);
}
public object VisitMemberExpression(MemberExpressionAst memberExpressionAst)
{
var newExpr = VisitElement(memberExpressionAst.Expression);
var newMember = VisitElement(memberExpressionAst.Member);
return new MemberExpressionAst(memberExpressionAst.Extent, newExpr, newMember, memberExpressionAst.Static);
}
public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst)
{
var newExpression = VisitElement(invokeMemberExpressionAst.Expression);
var newMethod = VisitElement(invokeMemberExpressionAst.Member);
var newArguments = VisitElements(invokeMemberExpressionAst.Arguments);
return new InvokeMemberExpressionAst(invokeMemberExpressionAst.Extent, newExpression, newMethod,
newArguments, invokeMemberExpressionAst.Static);
}
public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst)
{
var newStatementBlock = VisitElement(arrayExpressionAst.SubExpression);
return new ArrayExpressionAst(arrayExpressionAst.Extent, newStatementBlock);
}
public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst)
{
var newArrayElements = VisitElements(arrayLiteralAst.Elements);
return new ArrayLiteralAst(arrayLiteralAst.Extent, newArrayElements);
}
public object VisitHashtable(HashtableAst hashtableAst)
{
var newKeyValuePairs = new List>();
foreach (var keyValuePair in hashtableAst.KeyValuePairs)
{
var newKey = VisitElement(keyValuePair.Item1);
var newValue = VisitElement(keyValuePair.Item2);
newKeyValuePairs.Add(Tuple.Create(newKey, newValue));
}
return new HashtableAst(hashtableAst.Extent, newKeyValuePairs);
}
public object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
{
var newScriptBlock = VisitElement(scriptBlockExpressionAst.ScriptBlock);
return new ScriptBlockExpressionAst(scriptBlockExpressionAst.Extent, newScriptBlock);
}
public object VisitParenExpression(ParenExpressionAst parenExpressionAst)
{
var newPipeline = VisitElement(parenExpressionAst.Pipeline);
return new ParenExpressionAst(parenExpressionAst.Extent, newPipeline);
}
public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst)
{
return new ExpandableStringExpressionAst(expandableStringExpressionAst.Extent,
expandableStringExpressionAst.Value,
expandableStringExpressionAst.StringConstantType);
}
public object VisitIndexExpression(IndexExpressionAst indexExpressionAst)
{
var newTargetExpression = VisitElement(indexExpressionAst.Target);
var newIndexExpression = VisitElement(indexExpressionAst.Index);
return new IndexExpressionAst(indexExpressionAst.Extent, newTargetExpression, newIndexExpression);
}
public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst)
{
var newAttribute = VisitElement(attributedExpressionAst.Attribute);
var newChild = VisitElement(attributedExpressionAst.Child);
return new AttributedExpressionAst(attributedExpressionAst.Extent, newAttribute, newChild);
}
public object VisitBlockStatement(BlockStatementAst blockStatementAst)
{
var newBody = VisitElement(blockStatementAst.Body);
return new BlockStatementAst(blockStatementAst.Extent, blockStatementAst.Kind, newBody);
}
}
}