test/perf/Invoke-IncludeMethodSuite.ps1

#requires -version 7.0
<#
.SYNOPSIS
    Measures qbuild on one workspace fixture whose root map pulls in its project maps
    either with #include or by dot-sourcing them. Emits JSON.
.DESCRIPTION
    Run by Compare-IncludeMethods.ps1 in a fresh pwsh process per fixture, variant and
    round. Both variants expose the projects as p01..pNN, so the same command lines
    work on both. Cache states are the same as in Invoke-PerfSuite.ps1:
      warm - repeated call in a loaded session
      edited - a project map was just touched, so the discovery cache is rebuilt
      new-shell - fresh pwsh: module import + first command
.EXAMPLE
    ./Invoke-IncludeMethodSuite.ps1 -Directory $env:TEMP/x/workspace-M/dotsource -Fixture workspace-M -Variant dotsource
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string]$Directory,
    [Parameter(Mandatory = $true)]
    [string]$Fixture,
    [Parameter(Mandatory = $true)]
    [ValidateSet('include', 'dotsource')]
    [string]$Variant,
    [string]$ModulePath = (Join-Path $PSScriptRoot '..\..\configmap.psm1'),
    [ValidateRange(3, 10000)]
    [int]$Iterations = 30,
    [double]$ScenarioBudgetSeconds = 10,
    [ValidateRange(1, 1000)]
    [int]$NewShellIterations = 8,
    [string]$OutFile
)

$ErrorActionPreference = 'Stop'

Get-ChildItem env: | Where-Object Name -like 'QCONF_*' | ForEach-Object { Remove-Item "env:$($_.Name)" }

. (Join-Path $PSScriptRoot 'perf-measure.ps1')

$ModulePath = (Resolve-Path $ModulePath).Path
$Directory = (Resolve-Path $Directory).Path
Import-Module $ModulePath -Force

function Measure-NewShellRun {
    <#
    .SYNOPSIS
        Times `qbuild <entry>` as the first command of a fresh pwsh, like a CLI invocation.
    #>

    param([string]$Fixture, [string]$Directory, [string]$Entry)

    $probe = {
        param($ModulePath, $Directory, $Entry)
        Get-ChildItem env: | Where-Object Name -like 'QCONF_*' | ForEach-Object { Remove-Item "env:$($_.Name)" }
        Import-Module $ModulePath
        Set-Location $Directory
        $before = [GC]::GetTotalAllocatedBytes($true)
        $sw = [System.Diagnostics.Stopwatch]::StartNew()
        $null = qbuild $Entry *>&1
        $runMs = $sw.Elapsed.TotalMilliseconds
        $alloc = [GC]::GetTotalAllocatedBytes($true) - $before
        @{ runMs = $runMs; alloc = $alloc } | ConvertTo-Json -Compress
    }
    $command = "& { $probe } '$ModulePath' '$Directory' '$Entry'"
    $encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
    $name = "new-shell: qbuild $Entry"

    $times = [System.Collections.Generic.List[double]]::new()
    $allocs = [System.Collections.Generic.List[long]]::new()
    try {
        $null = pwsh -NoProfile -NonInteractive -EncodedCommand $encoded
        foreach ($i in 1..$NewShellIterations) {
            $r = pwsh -NoProfile -NonInteractive -EncodedCommand $encoded | Select-Object -Last 1 | ConvertFrom-Json
            $times.Add($r.runMs)
            $allocs.Add($r.alloc)
        }
    }
    catch {
        Write-Host (" {0,-14} {1,-34} FAILED: {2}" -f $Fixture, $name, $_) -ForegroundColor Yellow
        return [pscustomobject]@{ Scenario = $name; Fixture = $Fixture; Gate = $false; Error = "$_" }
    }

    New-ScenarioResult -Name $name -Fixture $Fixture -Gate $false -Times $times -Allocs $allocs
}

$results = [System.Collections.Generic.List[object]]::new()
$startLocation = Get-Location
try {
    Set-Location $Directory
    $rootMap = Join-Path $Directory '.build.map.ps1'
    $childMap = Join-Path $Directory 'p03\.build.map.ps1'
    $touch = { (Get-Item $childMap).LastWriteTimeUtc = [datetime]::UtcNow }.GetNewClosure()

    $results.Add((Measure-Scenario -Name 'load root map' -Fixture $Fixture -Gate $false -Action {
                Import-ConfigMap -Map $rootMap -LookUp:$false
            }))

    $list = { qbuild list 6>&1 | Out-String }
    $results.Add((Measure-Scenario -Name 'list (warm)' -Fixture $Fixture -Action $list))
    $results.Add((Measure-Scenario -Name 'list (edited)' -Fixture $Fixture -Action $list -Setup $touch))

    $tabs = [ordered]@{
        'TAB entry'       = 'qbuild '
        'TAB nested'      = 'qbuild p03.tools.db.'
        'TAB param name'  = 'qbuild p03.build -'
        'TAB param value' = 'qbuild p03.build -Configuration '
        'TAB pkg script'  = 'qbuild p03.web '
    }
    foreach ($kv in $tabs.GetEnumerator()) {
        $results.Add((Measure-Scenario -Name "$($kv.Key) (warm)" -Fixture $Fixture -Action { param($l) Measure-Tab $l } -ArgumentList $kv.Value))
    }
    $results.Add((Measure-Scenario -Name 'TAB entry (edited)' -Fixture $Fixture -Action { Measure-Tab 'qbuild ' } -Setup $touch))

    $results.Add((Measure-Scenario -Name 'run p03.task1 (warm)' -Fixture $Fixture -Action { qbuild p03.task1 *>&1 | Out-Null }))

    foreach ($r in Measure-NewShell -Fixture $Fixture -Directory $Directory -Line 'qbuild ') { $results.Add($r) }
    $results.Add((Measure-NewShellRun -Fixture $Fixture -Directory $Directory -Entry 'p03.task1'))
}
finally {
    Set-Location $startLocation
}

$report = [ordered]@{
    variant    = $Variant
    fixture    = $Fixture
    pwsh       = $PSVersionTable.PSVersion.ToString()
    os         = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
    iterations = $Iterations
    results    = $results
}

if ($OutFile) {
    $report | ConvertTo-Json -Depth 5 | Set-Content $OutFile
}
else {
    $results | Format-Table -AutoSize | Out-String | Write-Host
}