Private/RunTiming.ps1

# How long a Run took, and each Check in it.
#
# A Run that takes four minutes on one machine and fourteen on another says something of
# the second, and a Technician waiting at the window wants to know which Check is the
# slow one. So the Run measures every Check, both Parts and itself, and says it three
# times: in the window as it goes, in the box at the top of the Report, and as a table
# among the evidence. Measured by the Run, and not by a Check: a Check does not know how
# long it takes, and has no business knowing.

function Format-RunDuration {
    <#
    .SYNOPSIS
        A length of time as a Technician reads it: "0,7 s" below ten seconds, whole
        seconds up to two minutes, minutes and seconds up to an hour, hours and minutes
        from there. Empty for none. Pure.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([AllowNull()]$Seconds)

    $number = ConvertTo-Number $Seconds
    if ($null -eq $number) { return '' }
    $seconds = [math]::Max([double]0, [double]$number)
    if ($seconds -lt 10)  { return (Get-Text 'Value.Duration.SecondsTenth' $seconds) }
    if ($seconds -lt 120) { return (Get-Text 'Value.Duration.Seconds' ([int][math]::Round($seconds))) }
    $whole = [int][math]::Round($seconds)
    if ($whole -lt 3600) { return (Get-Text 'Value.Duration.Minutes' ([int][math]::Floor($whole / 60)) ($whole % 60)) }
    Get-Text 'Value.Duration.Hours' ([int][math]::Floor($whole / 3600)) ([int][math]::Floor(($whole % 3600) / 60))
}

function New-RunTiming {
    <#
    .SYNOPSIS
        One Check's time: its name, the Part it ran in, and its seconds. Pure.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param(
        [Parameter(Mandatory)][AllowEmptyString()][string]$Check,
        [ValidateSet('Main', 'Elevated')][string]$Part = 'Main',
        [double]$Seconds = 0
    )

    [pscustomobject]@{ Check = $Check; Part = $Part; Seconds = [math]::Round([math]::Max([double]0, $Seconds), 2) }
}

function Get-RunTimingRow {
    <#
    .SYNOPSIS
        How long the Run took, as a row of the box at the top of the Report: Group, Name,
        Value. Nothing where nothing was measured. Pure.
    .DESCRIPTION
        Until the Report was written: the time it takes to write it is the only part of a
        Run the Report cannot know. The Administrator Part is said beside it where there
        was one, being what a Technician waited for at the prompt.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()]$Seconds, [AllowNull()]$ElevatedSeconds)

    $total = Format-RunDuration -Seconds $Seconds
    if (-not $total) { return }
    $value = $total
    $admin = Format-RunDuration -Seconds $ElevatedSeconds
    if ($admin) { $value = (Get-Text 'Run.DurationWithAdmin') -f $total, $admin }
    [pscustomobject]@{ Group = (Get-Text 'System.Info.Group.Run'); Name = (Get-Text 'Run.Duration'); Value = $value }
}

function New-RunTimingSection {
    <#
    .SYNOPSIS
        Every Check of the Run with its time, the longest first, and its share of the
        Run. Nothing where nothing was measured. Pure.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()][AllowEmptyCollection()]$Timing, [AllowNull()]$Seconds)

    $all = @($Timing | Where-Object { $_ -and "$($_.Check)".Trim() })
    if (-not $all.Count) { return }
    $total = ConvertTo-Number $Seconds
    $rows = @($all | Sort-Object @{ Expression = { [double]$_.Seconds }; Descending = $true }, Check | ForEach-Object {
        $row = [ordered]@{}
        $row[(Get-Text 'Column.Run.Check')] = "$($_.Check)"
        $row[(Get-Text 'Column.Run.Part')]  = $(if ("$($_.Part)" -eq 'Elevated') { Get-Text 'Value.Run.PartAdmin' } else { Get-Text 'Value.Run.PartMain' })
        $row[(Get-Text 'Column.Run.Took')]  = '{0:N1}' -f [double]$_.Seconds
        $row[(Get-Text 'Column.Run.Share')] = $(if ($total -gt 0) { '{0:N0} %' -f ([double]$_.Seconds / $total * 100) } else { '' })
        [pscustomobject]$row
    })
    New-Section -Title (Get-Text 'Title.Run.Timing') -Row $rows -Help (Get-Text 'Help.Run.Timing')
}