Merge branch 'master' of https://github.com/KevinMarquette/Chronometer
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
29
Chronometer/Private/Write-ScriptLine.ps1
Normal file
29
Chronometer/Private/Write-ScriptLine.ps1
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Chronometer/Public/Format-Chronometer.ps1
Normal file
50
Chronometer/Public/Format-Chronometer.ps1
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
21
README.md
21
README.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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" {
|
||||||
{[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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user