From 17d2c57382e1badc22a34d3a78d7811aaf4e22cd Mon Sep 17 00:00:00 2001 From: KevinMarquette Date: Fri, 3 Feb 2017 00:16:08 -0800 Subject: [PATCH 1/5] Added Chronometer lcass --- Chronometer/Classes/Chronometer.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Chronometer/Classes/Chronometer.ps1 diff --git a/Chronometer/Classes/Chronometer.ps1 b/Chronometer/Classes/Chronometer.ps1 new file mode 100644 index 0000000..42644c1 --- /dev/null +++ b/Chronometer/Classes/Chronometer.ps1 @@ -0,0 +1,10 @@ +class Chronometer +{ + [hashtable]$FileMap = @{} + + [void]AddFile([string[]]$Path) + { + + } + +} \ No newline at end of file From f532089adaa9d4c56637c8692f97ea22d75523ae Mon Sep 17 00:00:00 2001 From: KevinMarquette Date: Fri, 3 Feb 2017 00:48:30 -0800 Subject: [PATCH 2/5] Refactoring --- Chronometer/Classes/Chronometer.ps1 | 70 +++++++++++++++++++++++++- Chronometer/Public/Get-Chronometer.ps1 | 38 ++------------ 2 files changed, 72 insertions(+), 36 deletions(-) diff --git a/Chronometer/Classes/Chronometer.ps1 b/Chronometer/Classes/Chronometer.ps1 index 42644c1..43386d1 100644 --- a/Chronometer/Classes/Chronometer.ps1 +++ b/Chronometer/Classes/Chronometer.ps1 @@ -1,10 +1,76 @@ class Chronometer { [hashtable]$FileMap = @{} + $Breakpoint = @() - [void]AddFile([string[]]$Path) + [void]AddBreakpoint([string[]]$Path) { + foreach($file in (Resolve-Path $Path -ea 0)) + { + $script = [MonitoredScript]@{Path=$file.Path} + $lines = $script.SetScript($file) + $this.fileMap[$file.Path] = $script + + $breakpointParam = @{ + Script = $file + Line = (1..$lines) + Action = {[ScriptProfiler]::RecordExecution( $_) } + } + $this.breakPoint += Set-PSBreakpoint @breakpointParam + } + } + + [void]ClearBreakpoint() + { + Remove-PSBreakpoint $this.Breakpointbreakpoint + } + + [void] AddExecution([hashtable]$Execution) + { + $this.FileMap[$Execution.Breakpoint.Script].AddExecution($Execution) + } +} + +class MonitoredScript +{ + [string]$Path + [System.Collections.ArrayList]$Line + + $lastNode = $null + $lastRecord = $null + + MonitoredScript() + { + $this.Line = [System.Collections.ArrayList]::New() + } + + [int] SetScript([string]$Path) + { + Get-Content -Path $Path | %{ $this.Line.Add( [ScriptLine]@{text=$_})} + return $this.Line.Count() + } + + [void] AddExecution([hashtable]$node) + { + $record = $this.Line[$node.Breakpoint.Line-1] + $record.LineNumber = $node.Breakpoint.Line - 1 + + if($this.lastNode) + { + $duration = $node.ElapsedMilliseconds - $this.lastNode.ElapsedMilliseconds + } + else + { + $duration = $node.ElapsedMilliseconds + } + + if($this.lastRecord) + { + $this.lastRecord.AddExecutionTime($duration) + } + + $this.lastRecord = $record + $this.lastNode = $node } - } \ No newline at end of file diff --git a/Chronometer/Public/Get-Chronometer.ps1 b/Chronometer/Public/Get-Chronometer.ps1 index a57d7ca..b5bcadb 100644 --- a/Chronometer/Public/Get-Chronometer.ps1 +++ b/Chronometer/Public/Get-Chronometer.ps1 @@ -21,47 +21,17 @@ function Get-Chronometer $ScriptBlock ) - $breakPoint = @() - $fileMap = @{} - - foreach($file in (Resolve-Path $Path -ea 0)) - { - $fileMap[$file.Path] = @( Get-Content -Path $file | %{[ScriptLine]@{text=$_;path=$file.path}}) - - $lines = $fileMap[$file.Path].count - $breakPoint += Set-PSBreakpoint -Script $file -Line (1..$lines) -Action {[ScriptProfiler]::RecordExecution( $_) } - } + $Chronometer = [Chronometer]::New() + $breakPoint = $Chronometer.AddBreakpoint($Path) [ScriptProfiler]::Start() [void] $ScriptBlock.Invoke() - Remove-PSBreakpoint $breakpoint - - #$fileMap | ConvertTo-Json - + $Chronometer.ClearBreakpoint() foreach($node in [ScriptProfiler]::Queue.GetEnumerator()) { - $record = $fileMap[$node.Breakpoint.Script][$node.Breakpoint.Line-1] - $record.LineNumber = $node.Breakpoint.Line - 1 - - if($lastNode) - { - $duration = $node.ElapsedMilliseconds - $lastNode.ElapsedMilliseconds - } - else - { - $duration = $node.ElapsedMilliseconds - } - - - if($lastRecord) - { - $lastRecord.AddExecutionTime($duration) - } - - $lastRecord = $record - $lastNode = $node + $Chronometer.AddExecution($node) } foreach($script in $fileMap.Keys) From af7e4143cc975f0b1b6315b95f29df023cdaf8fc Mon Sep 17 00:00:00 2001 From: KevinMarquette Date: Sat, 4 Feb 2017 20:53:08 -0800 Subject: [PATCH 3/5] reworked class importing, passing pester tests skip ci --- Chronometer/Classes/Chronometer.ps1 | 14 +++++++++++--- Chronometer/chronometer.psm1 | 25 ++++++++++++++++++------- Tests/Help.Tests.ps1 | 3 ++- Tests/Project.Tests.ps1 | 14 +++++++++++++- Tests/Unit.Tests.ps1 | 5 +++-- 5 files changed, 47 insertions(+), 14 deletions(-) diff --git a/Chronometer/Classes/Chronometer.ps1 b/Chronometer/Classes/Chronometer.ps1 index 43386d1..94ad8ed 100644 --- a/Chronometer/Classes/Chronometer.ps1 +++ b/Chronometer/Classes/Chronometer.ps1 @@ -23,12 +23,20 @@ class Chronometer [void]ClearBreakpoint() { - Remove-PSBreakpoint $this.Breakpointbreakpoint + if($this.Breakpoint -ne $null -and $this.Breakpoint.count -gt 0) + { + Remove-PSBreakpoint -Breakpoint $this.Breakpoint + } + } [void] AddExecution([hashtable]$Execution) { - $this.FileMap[$Execution.Breakpoint.Script].AddExecution($Execution) + $script = $Execution.Breakpoint.Script + if($this.FileMap.ContainsKey($script)) + { + $this.FileMap[$script].AddExecution($Execution) + } } } @@ -48,7 +56,7 @@ class MonitoredScript [int] SetScript([string]$Path) { Get-Content -Path $Path | %{ $this.Line.Add( [ScriptLine]@{text=$_})} - return $this.Line.Count() + return $this.Line.Count } [void] AddExecution([hashtable]$node) diff --git a/Chronometer/chronometer.psm1 b/Chronometer/chronometer.psm1 index 3b06336..f63a722 100644 --- a/Chronometer/chronometer.psm1 +++ b/Chronometer/chronometer.psm1 @@ -1,9 +1,23 @@ #Requires -Version 5.0 +[cmdletbinding()] +param() -Write-Verbose "Importing Functions" +Write-Verbose $PSScriptRoot +Write-Verbose 'Import Classes in order because of dependencies' +$classList = @( + 'ScriptLine', + 'ScriptProfiler', + 'Chronometer' +) -# Import everything in sub folders folder -foreach($folder in @('classes', 'private', 'public','includes')) +foreach($class in $classList) +{ + Write-Verbose " Class: $class" + . "$psscriptroot\classes\$class.ps1" +} + +Write-Verbose 'Import everything in sub folders folder' +foreach($folder in @('private', 'public','includes')) { $root = Join-Path -Path $PSScriptRoot -ChildPath $folder if(Test-Path -Path $root) @@ -13,12 +27,9 @@ foreach($folder in @('classes', 'private', 'public','includes')) # dot source each file $files | where-Object{ $_.name -NotLike '*.Tests.ps1'} | - ForEach-Object{Write-Verbose $_.name; . $_.FullName} + ForEach-Object{Write-Verbose $_.basename; . $_.FullName} } } Export-ModuleMember -function (Get-ChildItem -Path "$PSScriptRoot\public\*.ps1").basename -# Hack for my build system that had a conflit with the keyword node -New-Alias -Name 'DiGraph' -Value 'Graph' -ErrorAction SilentlyContinue -Export-ModuleMember -Alias 'DiGraph' diff --git a/Tests/Help.Tests.ps1 b/Tests/Help.Tests.ps1 index 6f4d3fb..7a61ead 100644 --- a/Tests/Help.Tests.ps1 +++ b/Tests/Help.Tests.ps1 @@ -2,9 +2,10 @@ $projectRoot = Resolve-Path "$PSScriptRoot\.." $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") $moduleName = Split-Path $moduleRoot -Leaf -Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force Describe "Help tests for $moduleName" -Tags Build { + + Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force $functions = Get-Command -Module $moduleName $help = $functions | %{Get-Help $_.name} diff --git a/Tests/Project.Tests.ps1 b/Tests/Project.Tests.ps1 index 5ed72b3..c12f8bd 100644 --- a/Tests/Project.Tests.ps1 +++ b/Tests/Project.Tests.ps1 @@ -4,7 +4,7 @@ $moduleName = Split-Path $moduleRoot -Leaf Describe "General project validation: $moduleName" -Tags Build { - $scripts = Get-ChildItem $projectRoot -Include *.ps1,*.psm1,*.psd1 -Recurse + $scripts = Get-ChildItem $projectRoot -Include *.ps1,*.psm1,*.psd1 -Recurse | where fullname -notmatch 'classes' # TestCases are splatted to the script so we need hashtables $testCase = $scripts | Foreach-Object{@{file=$_}} @@ -19,6 +19,18 @@ Describe "General project validation: $moduleName" -Tags Build { $errors.Count | Should Be 0 } + It "Classes are valid" { + $classes = Get-ChildItem $projectRoot -Include *.ps1,*.psm1,*.psd1 -Recurse | where fullname -match 'classes' + + # Must be imported togehter incase they depend on each other + $contents = Get-Content -Path $classes.FullName | Out-String + + $errors = $null + $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors) + $errors.Count | Should Be 0 + } + + It "Module '$moduleName' can import cleanly" { {Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force } | Should Not Throw } diff --git a/Tests/Unit.Tests.ps1 b/Tests/Unit.Tests.ps1 index 0ea483b..ecb61cc 100644 --- a/Tests/Unit.Tests.ps1 +++ b/Tests/Unit.Tests.ps1 @@ -2,12 +2,13 @@ $projectRoot = Resolve-Path "$PSScriptRoot\.." $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psd1") $moduleName = Split-Path $moduleRoot -Leaf -Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force - Describe "Basic unit tests" -Tags Build { + + Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force Context "Function: Get-Chronometer" { it "Does not throw" { + # Get-Chronometer -Path ScratchFiles\example.ps1 -Script {"Test"} {Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {"Test"} } | Should Not Throw } } From 1b836300015303cffdd9383117c9d4ff466e46c9 Mon Sep 17 00:00:00 2001 From: KevinMarquette Date: Sat, 4 Feb 2017 23:18:43 -0800 Subject: [PATCH 4/5] Everything is stable again. Some refactoring work is still in progress !deploy --- Chronometer/Classes/Chronometer.ps1 | 10 +++++++-- Chronometer/Public/Get-Chronometer.ps1 | 30 +++++++++++++++----------- README.md | 3 ++- Tests/Unit.Tests.ps1 | 8 ++++++- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/Chronometer/Classes/Chronometer.ps1 b/Chronometer/Classes/Chronometer.ps1 index 94ad8ed..159ffe2 100644 --- a/Chronometer/Classes/Chronometer.ps1 +++ b/Chronometer/Classes/Chronometer.ps1 @@ -35,9 +35,15 @@ class Chronometer $script = $Execution.Breakpoint.Script if($this.FileMap.ContainsKey($script)) { + # Each script tracks it's own execution times $this.FileMap[$script].AddExecution($Execution) } } + + [MonitoredScript[]] GetResults() + { + return $this.FileMap.Values + } } class MonitoredScript @@ -45,8 +51,8 @@ class MonitoredScript [string]$Path [System.Collections.ArrayList]$Line - $lastNode = $null - $lastRecord = $null + hidden $lastNode = $null + hidden $lastRecord = $null MonitoredScript() { diff --git a/Chronometer/Public/Get-Chronometer.ps1 b/Chronometer/Public/Get-Chronometer.ps1 index b5bcadb..bc95fa2 100644 --- a/Chronometer/Public/Get-Chronometer.ps1 +++ b/Chronometer/Public/Get-Chronometer.ps1 @@ -22,23 +22,29 @@ function Get-Chronometer ) $Chronometer = [Chronometer]::New() - $breakPoint = $Chronometer.AddBreakpoint($Path) - [ScriptProfiler]::Start() - [void] $ScriptBlock.Invoke() + Write-Verbose "Setting breapoints" + $Chronometer.AddBreakpoint($Path) - $Chronometer.ClearBreakpoint() - - foreach($node in [ScriptProfiler]::Queue.GetEnumerator()) + if($Chronometer.breakPoint -ne $null) { - $Chronometer.AddExecution($node) - } + Write-Verbose "Executing Script" + [ScriptProfiler]::Start() + [void] $ScriptBlock.Invoke() - foreach($script in $fileMap.Keys) - { - foreach($line in $fileMap[$script]) + Write-Verbose "Clearing Breapoints" + $Chronometer.ClearBreakpoint() + + Write-Verbose "Processing data" + foreach($node in [ScriptProfiler]::Queue.GetEnumerator()) { - Write-Output $line + $Chronometer.AddExecution($node) } + + Write-Output $Chronometer.GetResults() + } + else + { + Write-Warning "Parsing files did not result in any breakpoints" } } diff --git a/README.md b/README.md index c18dda7..5360d69 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Provide a script file and a command to execute. $path = myscript.ps1 Get-Chronometer -Path $path -Script {. .\myscript.ps1} -OutVariable report - $report.ToString() + $report.line | % tostring + 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 diff --git a/Tests/Unit.Tests.ps1 b/Tests/Unit.Tests.ps1 index ecb61cc..81d9a4f 100644 --- a/Tests/Unit.Tests.ps1 +++ b/Tests/Unit.Tests.ps1 @@ -3,7 +3,7 @@ $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psd1") $moduleName = Split-Path $moduleRoot -Leaf Describe "Basic unit tests" -Tags Build { - + Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force Context "Function: Get-Chronometer" { @@ -11,6 +11,12 @@ Describe "Basic unit tests" -Tags Build { # Get-Chronometer -Path ScratchFiles\example.ps1 -Script {"Test"} {Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {"Test"} } | Should Not Throw } + + it "Executes a script and gives results" { + # Get-Chronometer -Path ScratchFiles\example.ps1 -Script {"Test"} + $results = Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {. "$PSScriptRoot\..\ScratchFiles\example.ps1"} + $results | Should Not BeNullOrEmpty + } } InModuleScope $moduleName { From baa544d9245d11fa0c403b58d0bbe00c24a5c793 Mon Sep 17 00:00:00 2001 From: KevinMarquette Date: Sun, 5 Feb 2017 00:00:58 -0800 Subject: [PATCH 5/5] reworked some files skip ci --- Chronometer/Classes/Chronometer.ps1 | 42 --------------------- Chronometer/Classes/MonitoredScript.ps1 | 49 +++++++++++++++++++++++++ Chronometer/chronometer.psm1 | 1 + Tests/Unit.Tests.ps1 | 8 ++++ 4 files changed, 58 insertions(+), 42 deletions(-) create mode 100644 Chronometer/Classes/MonitoredScript.ps1 diff --git a/Chronometer/Classes/Chronometer.ps1 b/Chronometer/Classes/Chronometer.ps1 index 159ffe2..fe867df 100644 --- a/Chronometer/Classes/Chronometer.ps1 +++ b/Chronometer/Classes/Chronometer.ps1 @@ -46,45 +46,3 @@ class Chronometer } } -class MonitoredScript -{ - [string]$Path - [System.Collections.ArrayList]$Line - - hidden $lastNode = $null - hidden $lastRecord = $null - - MonitoredScript() - { - $this.Line = [System.Collections.ArrayList]::New() - } - - [int] SetScript([string]$Path) - { - Get-Content -Path $Path | %{ $this.Line.Add( [ScriptLine]@{text=$_})} - return $this.Line.Count - } - - [void] AddExecution([hashtable]$node) - { - $record = $this.Line[$node.Breakpoint.Line-1] - $record.LineNumber = $node.Breakpoint.Line - 1 - - if($this.lastNode) - { - $duration = $node.ElapsedMilliseconds - $this.lastNode.ElapsedMilliseconds - } - else - { - $duration = $node.ElapsedMilliseconds - } - - if($this.lastRecord) - { - $this.lastRecord.AddExecutionTime($duration) - } - - $this.lastRecord = $record - $this.lastNode = $node - } -} \ No newline at end of file diff --git a/Chronometer/Classes/MonitoredScript.ps1 b/Chronometer/Classes/MonitoredScript.ps1 new file mode 100644 index 0000000..c2c04d7 --- /dev/null +++ b/Chronometer/Classes/MonitoredScript.ps1 @@ -0,0 +1,49 @@ +class MonitoredScript +{ + [string]$Path + [System.Collections.Generic.List[ScriptLine]]$Line + + hidden $lastNode = $null + hidden $lastRecord = $null + + [float]$ExecutionTime = 0 + [int]$LinesOfCode = 0 + + MonitoredScript() + { + $this.Line =New-Object 'System.Collections.Generic.List[ScriptLine]' + } + + [int] SetScript([string]$Path) + { + Get-Content -Path $Path | %{ $this.Line.Add( [ScriptLine]@{text=$_;path=$path})} + $this.LinesOfCode = $this.Line.Count + return $this.LinesOfCode + } + + [void] AddExecution([hashtable]$node) + { + # Line numbers start at 1 but the array starts at 0 + $lineNumber = $node.Breakpoint.Line - 1 + $record = $this.Line[$lineNumber] + $record.LineNumber = $lineNumber + + if($this.lastNode) + { + $duration = $node.ElapsedMilliseconds - $this.lastNode.ElapsedMilliseconds + } + else + { + $duration = $node.ElapsedMilliseconds + } + + if($this.lastRecord) + { + $this.lastRecord.AddExecutionTime($duration) + $this.ExecutionTime += $duration + } + + $this.lastRecord = $record + $this.lastNode = $node + } +} \ No newline at end of file diff --git a/Chronometer/chronometer.psm1 b/Chronometer/chronometer.psm1 index f63a722..19d1b5c 100644 --- a/Chronometer/chronometer.psm1 +++ b/Chronometer/chronometer.psm1 @@ -7,6 +7,7 @@ Write-Verbose 'Import Classes in order because of dependencies' $classList = @( 'ScriptLine', 'ScriptProfiler', + 'MonitoredScript', 'Chronometer' ) diff --git a/Tests/Unit.Tests.ps1 b/Tests/Unit.Tests.ps1 index 81d9a4f..ece0505 100644 --- a/Tests/Unit.Tests.ps1 +++ b/Tests/Unit.Tests.ps1 @@ -42,5 +42,13 @@ Describe "Basic unit tests" -Tags Build { {[ScriptProfiler]::Start()} | Should Not Throw } } + + Context "Class: MonitoredScript" { + {[MonitoredScript]::New()} | Should Not Throw + } + + Context "Class: MonitoredScript" { + {[MonitoredScript]::SetScript("$projectRoot\scratchfiles\example.ps1")} | Should Not Throw + } } } \ No newline at end of file