Public/Timing/Measure-TimingSpan.ps1
|
function Measure-TimingSpan { <# .SYNOPSIS Time -Action as a span under the context's current node. .DESCRIPTION The primary timing verb. Finds-or-creates a child named -Name under the current node, pushes it (so anything -Action itself times nests beneath), runs -Action under a stopwatch, accumulates the elapsed into the node, sets the terminal status, and pops - on both the success and failure paths. The timer only observes: any exception -Action throws propagates unchanged (exactly like Invoke-WithPhaseTimer), so wrapping existing control flow never alters it. The failing node still records its partial elapsed and is marked Failed before the rethrow, so a report emitted from a finally shows where the run got to. Accumulation is by name-within-parent: calling Measure-TimingSpan with the same -Name under the same parent again resolves the same node and adds to its running total (the per-VM-loop semantics of the 2-level framework, now at any depth). Failed is sticky - a later success against a node that already failed does not clear the flag. Output of -Action streams to the caller, so Measure-TimingSpan is transparent to a wrapped scriptblock that produces a value. .PARAMETER Tree The context returned by New-TimingSpanTree. .PARAMETER Name Span name; unique within the current node's children. .PARAMETER Action The work to time. Runs as-is; exceptions propagate. .PARAMETER Source Optional provenance tag applied to the node on first creation. .EXAMPLE Measure-TimingSpan -Tree $tree -Name 'Phase 1' -Action { Measure-TimingSpan -Tree $tree -Name 'boot VM' -Action { Start-Vm } } # 'boot VM' nests under 'Phase 1' because the outer Action runs while # 'Phase 1' is the current node. #> [CmdletBinding()] param( [Parameter(Mandatory)] $Tree, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(Mandatory)] [scriptblock] $Action, [string] $Source ) $current = $Tree.Stack.Peek() $node = Resolve-TimingSpanChildNode ` -Context $Tree -Parent $current -Name $Name -Source $Source # Mark Running for a mid-run report, but never overwrite a sticky Failed # from an earlier iteration of the same span - clearing it here would let # a later success hide the bad run. if ($node.Status -ne 'Failed') { $node.Status = 'Running' } $Tree.Stack.Push($node) $sw = [System.Diagnostics.Stopwatch]::StartNew() try { & $Action $sw.Stop() Add-TimingSpanNodeElapsed -Node $node -ElapsedMs $sw.ElapsedMilliseconds } catch { # Capture the partial duration and flag Failed before re-throwing so # the report reflects the failing span; the exception is not swallowed. $sw.Stop() Add-TimingSpanNodeElapsed -Node $node -ElapsedMs $sw.ElapsedMilliseconds -Failed throw } finally { # Pop on every path so the stack is balanced whether -Action returned # or threw; the parent node becomes current again. $Tree.Stack.Pop() | Out-Null } } |