// // 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); } } }