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

View File

@@ -46,4 +46,36 @@ class MonitoredScript
$this.lastRecord = $record $this.lastRecord = $record
$this.lastNode = $node $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[]] [string[]]
$Path, $Path,
# Line numbers within the script file to measure
[int[]]
$LineNumber = $null,
# The script to start the scrupt or execute other commands # The script to start the scrupt or execute other commands
[alias('Script','CommandScript')] [alias('Script','CommandScript')]
[scriptblock] [scriptblock]
@@ -24,7 +28,7 @@ function Get-Chronometer
$Chronometer = [Chronometer]::New() $Chronometer = [Chronometer]::New()
Write-Verbose "Setting breapoints" Write-Verbose "Setting breapoints"
$Chronometer.AddBreakpoint($Path) $Chronometer.AddBreakpoint($Path,$LineNumber)
if($Chronometer.breakPoint -ne $null) 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 A module for measuring performance of Powershell scripts, one line at a time
## Project status ## 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 # Getting started
## Prerequirements ## Prerequirements
You need to have Powershell 5.0 or newer. This module uses classes. You need to have Powershell 5.0 or newer. This module uses classes.
## Installing Chronometer ## 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 ## Basic usage
Provide a script file and a command to execute. Provide a script file and a command to execute.
$path = myscript.ps1 $path = myscript.ps1
Get-Chronometer -Path $path -Script {. .\myscript.ps1} -OutVariable report $Chronometer = Get-Chronometer -Path $path -Script {. .\myscript.ps1}
$report.line | % tostring $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 ## 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. 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" "test string"
sleep -Milliseconds 3 sleep -Milliseconds 3
} }
sleep -Milliseconds 12 sleep -Milliseconds 12
sleep -Milliseconds 5 sleep -Milliseconds 120
$test = 1+1 $test = 1+1
$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 = Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {. "$PSScriptRoot\..\ScratchFiles\example.ps1"}
$results | Should Not BeNullOrEmpty $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 { InModuleScope $moduleName {
@@ -44,11 +67,17 @@ Describe "Basic unit tests" -Tags Build {
} }
Context "Class: MonitoredScript" { Context "Class: MonitoredScript" {
it "Creates an object" {
{[MonitoredScript]::New()} | Should Not Throw {[MonitoredScript]::New()} | Should Not Throw
} }
Context "Class: MonitoredScript" { it "SetScript()" {
{[MonitoredScript]::SetScript("$projectRoot\scratchfiles\example.ps1")} | Should Not Throw pushd $projectRoot
$monitor = [MonitoredScript]::New()
{$monitor.SetScript(".\scratchfiles\example.ps1")} | Should Not Throw
popd
} }
} }
}
} }

View File

@@ -33,7 +33,7 @@ Task Init {
Task UnitTests -Depends Init { Task UnitTests -Depends Init {
$lines $lines
'Running quick unit tests to fail early if there is an error' '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) if($TestResults.FailedCount -gt 0)
{ {
@@ -47,7 +47,7 @@ Task Test -Depends UnitTests {
"`n`tSTATUS: Testing with PowerShell $PSVersion" "`n`tSTATUS: Testing with PowerShell $PSVersion"
# Gather test results. Store them in a variable and file # 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? # In Appveyor? Upload our tests! #Abstract this into a function?
If($ENV:BHBuildSystem -eq 'AppVeyor') If($ENV:BHBuildSystem -eq 'AppVeyor')