This commit is contained in:
KevinMarquette
2017-02-05 15:24:36 -08:00
10 changed files with 184 additions and 16 deletions

View File

@@ -3,18 +3,26 @@ class Chronometer
[hashtable]$FileMap = @{}
$Breakpoint = @()
[void]AddBreakpoint([string[]]$Path)
[void]AddBreakpoint([string[]]$Path, [int[]]$LineNumber)
{
foreach($file in (Resolve-Path $Path -ea 0))
{
$script = [MonitoredScript]@{Path=$file.Path}
$lines = $script.SetScript($file)
if($LineNumber -ne $null)
{
$bpLine = $LineNumber
}
else
{
$bpLine = (1..$lines)
}
$this.fileMap[$file.Path] = $script
$breakpointParam = @{
Script = $file
Line = (1..$lines)
Line = $bpLine
Action = {[ScriptProfiler]::RecordExecution( $_) }
}
$this.breakPoint += Set-PSBreakpoint @breakpointParam
@@ -42,6 +50,10 @@ class Chronometer
[MonitoredScript[]] GetResults()
{
foreach($node in $this.FileMap.Values)
{
$node.PostProcessing()
}
return $this.FileMap.Values
}
}

View File

@@ -46,4 +46,36 @@ class MonitoredScript
$this.lastRecord = $record
$this.lastNode = $node
}
[void] PostProcessing()
{
$this.lastNode = $null
$this.ExecutionTime = 0
foreach($node in $this.line)
{
$command = $node.text -replace '\s',''
switch -Regex ($command)
{
'^}$|^}#|^$' {
if($node.HitCount -eq 0)
{
$node.HitCount = $this.lastNode.HitCount
}
$node.Milliseconds = 0
$node.Average = 0
$this.lastNode = $node
}
'^{$|^{#}' {
$node.Milliseconds = 0
$node.Average = 0
$this.lastNode = $node
}
default {
$this.lastNode = $node
}
}
$this.ExecutionTime += $node.Milliseconds
}
}
}

View File

@@ -0,0 +1,29 @@
function Write-ScriptLine
{
param(
[scriptline]
$line,
$WarningAt = [int]::MaxValue,
$ErrorAt = [int]::MaxValue
)
if($line)
{
$Color = 'Green'
if($line.HitCount -eq 0)
{
$Color = 'Gray'
}
elseif($line.Average -ge $ErrorAt)
{
$Color = 'Red'
}
elseif($line.Average -ge $WarningAt)
{
$Color = 'Yellow'
}
Write-Host $line.toString() -ForegroundColor $Color
}
}

View File

@@ -0,0 +1,50 @@
function Format-Chronometer
{
<#
.Description
Generates a report from a Chronometer
.Example
$script = ls C:\workspace\PSGraph\PSGraph -Recurse -Filter *.ps1
$resultes = Get-Chronometer -Path $script.fullname -ScriptBlock {Invoke-Pester C:\workspace\PSGraph}
$results | Format-Chronometer -WarnAt 20 -ErrorAt 200
#>
[cmdletbinding()]
param(
# This is a MonitoredScript object from Get-Chronometer
[Parameter(
ValueFromPipeline=$true
)]
[MonitoredScript[]]
$InputObject,
# If the average time of a command is more than this, the output is yellow
[int]
$WarningAt = 20,
#If the average time of a comamand is more than this, the output is red
[int]
$ErrorAt = 200
)
begin {
$green = @{ForgroundColor='green'}
$grey = @{ForgroundColor='grey'}
$yellow = @{ForgroundColor='grey'}
$yellow = @{ForgroundColor='grey'}
$yellow = @{ForgroundColor='grey'}
}
process
{
foreach($script in $InputObject)
{
Write-Host ''
Write-Host "Script: $($script.Path)" -ForegroundColor Green
Write-Host "Execution Time: $($script.ExecutionTime)" -ForegroundColor Green
foreach($line in $script.line)
{
Write-ScriptLine $line -WarningAt $WarningAt -ErrorAt $ErrorAt
}
}
}
}

View File

@@ -15,6 +15,10 @@ function Get-Chronometer
[string[]]
$Path,
# Line numbers within the script file to measure
[int[]]
$LineNumber = $null,
# The script to start the scrupt or execute other commands
[alias('Script','CommandScript')]
[scriptblock]
@@ -24,7 +28,7 @@ function Get-Chronometer
$Chronometer = [Chronometer]::New()
Write-Verbose "Setting breapoints"
$Chronometer.AddBreakpoint($Path)
$Chronometer.AddBreakpoint($Path,$LineNumber)
if($Chronometer.breakPoint -ne $null)
{

Binary file not shown.

View File

@@ -2,24 +2,35 @@
A module for measuring performance of Powershell scripts, one line at a time
## Project status
Experimental. Just a working idea at the moment. Functions and argument names are still up in the air. Also don't consider it stable or tested. Use at your own risk.
Preview release. The core logic is fleshed out but more testing is needed.
# Getting started
## Prerequirements
You need to have Powershell 5.0 or newer. This module uses classes.
## Installing Chronometer
Place the Chronometer folder into your `$PSModulePath`. I will publish to the Powershell Gallery once the project is more stable.
This is published in the Powershell Gallery
Install-Module Chronometer
## Basic usage
Provide a script file and a command to execute.
$path = myscript.ps1
Get-Chronometer -Path $path -Script {. .\myscript.ps1} -OutVariable report
$report.line | % tostring
$Chronometer = Get-Chronometer -Path $path -Script {. .\myscript.ps1}
$Chronometer | Format-Chronometer
The user experience is important to me but I am working on the core logic right now. I will loop back to make it more intuitive and simple to use.
## Things to know
The `Path` can be any ps1 and the script can run any command. Ideally, you would either execute the script or load the script and execute a command inside it.
Here is a more complex example:
$script = ls C:\workspace\PSGraph\PSGraph -Recurse -Filter *.ps1
$Chronometer = @{
Path = $script.fullname
Script = {Invoke-Pester C:\workspace\PSGraph}
}
$results = Get-Chronometer @Chronometer
$results | Format-Chronometer

View File

@@ -10,7 +10,8 @@ foreach($n in 1..10)
"test string"
sleep -Milliseconds 3
}
sleep -Milliseconds 12
sleep -Milliseconds 5
sleep -Milliseconds 120
$test = 1+1
$test = 1+1

View File

@@ -17,6 +17,29 @@ Describe "Basic unit tests" -Tags Build {
$results = Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {. "$PSScriptRoot\..\ScratchFiles\example.ps1"}
$results | Should Not BeNullOrEmpty
}
it "Executes a script with linenumbers and gives results" {
# Get-Chronometer -Path ScratchFiles\example.ps1 -Script {"Test"}
$params = @{
Path = "$PSScriptRoot\..\ScratchFiles\example.ps1"
Script = {. "$PSScriptRoot\..\ScratchFiles\example.ps1"}
LineNumber = 2,3,5,6
}
$results = Get-Chronometer @params
$results | Should Not BeNullOrEmpty
}
}
Context "Function: Format-Chronometer" {
it "Does not throw" {
{$null | Format-Chronometer } | Should Not Throw
}
it "Can process a result object without throwing" {
$results = Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {. "$PSScriptRoot\..\ScratchFiles\example.ps1"}
$results | Format-Chronometer *>&1 | Should Not BeNullOrEmpty
}
}
InModuleScope $moduleName {
@@ -44,11 +67,17 @@ Describe "Basic unit tests" -Tags Build {
}
Context "Class: MonitoredScript" {
{[MonitoredScript]::New()} | Should Not Throw
it "Creates an object" {
{[MonitoredScript]::New()} | Should Not Throw
}
it "SetScript()" {
pushd $projectRoot
$monitor = [MonitoredScript]::New()
{$monitor.SetScript(".\scratchfiles\example.ps1")} | Should Not Throw
popd
}
}
Context "Class: MonitoredScript" {
{[MonitoredScript]::SetScript("$projectRoot\scratchfiles\example.ps1")} | Should Not Throw
}
}
}

View File

@@ -33,7 +33,7 @@ Task Init {
Task UnitTests -Depends Init {
$lines
'Running quick unit tests to fail early if there is an error'
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests\*unit* -PassThru -Tag Build -Show Failed
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests\*unit* -PassThru -Tag Build
if($TestResults.FailedCount -gt 0)
{
@@ -47,7 +47,7 @@ Task Test -Depends UnitTests {
"`n`tSTATUS: Testing with PowerShell $PSVersion"
# Gather test results. Store them in a variable and file
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests -PassThru -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -Tag Build -Show Failed
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests -PassThru -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -Tag Build
# In Appveyor? Upload our tests! #Abstract this into a function?
If($ENV:BHBuildSystem -eq 'AppVeyor')