In my last post I talked about the AST and how it was now possible to modify it using the visitor pattern. After talking to some of the Microsoft folks at the PowerShell Deep Dive, the idea was suggested that this would allow for the creation of a profiler for PowerShell scripts. I had written an advanced PowerShell function that could do the same thing but it required the user to insert calls to Measure-Block throughout their script. I’ve implemented a basic PowerShell profiler that does not require such manual modification.
There are two common types of profilers. The first is a sampling profiler. These profilers work by taking regular samples of the CPU or memory use of a particular process and then tracking the call stack at the sample. These are typically lower over head and do not require the modification of the code. Another common profiler type is an instrumentation profiler. These profilers work by injecting code into the assembly. When the code is executed the injected code is executed.
In this post we’ll look at how to create the latter; an instrumentation profiler.
First, we need to create a class that will be responsbile for tracking our profiling metric and will then be inserted into our script. In the case of this profiler, we can just use time as our profiling metric. The instrumentor class needs to record the time and location when it is called. Here’s a very basic implementation.
The Instrument method will be called and will record the sample. The script “extent” will be stored so that we can process this later. Now we need to modify the AST visitor so that it can inject call to our instrumentor throughout the script. We’ll just modify any script blocks that are passed in and inject calls to the Instrument method between each statement. We can then process all the times, determine the delta and then overlay our profiling results in a GUI. In the below method, the GetInstrumentAst creates a statement AST node that calls the Instrument method on a variable and passes in the script extents.
The GetInstrumentAst method looks like this.
Now the trick is to create the Instrumentor variable in the runspace before executing the script. This can be done with the SessionStateProxy of the property of the runspace. After running the script, we can grab the Instrumentor variable from the runspace and process the timings we have gathered during execution.
Once we have gathered the data during execution we can calculate the deltas between the samples and then overlay them in a simple GUI. Here’s a really basic GUI and the result of the above.
Just noticed there is bug somewhere…time is wrong above
The left side has the duration of the command on the right side. This is a very basic implementation. I’m going to spend some time looking into profiling individual statements. This would allow for far more complex profiling information. I also haven’t taken into account function counts or call stacks. I will certainly post again when I figure more out .:)