perf(diag): complete trustworthy Slice A capture tooling

Correct whole-frame GPU timestamps so they bracket only the accepted render transaction and associate delayed query results with the owning CPU frame. Add route-wide frame-history summaries, fixed-camera screenshot comparison, process counters, contention traces, a pinned Arwic workload, and credential-safe launch disclosure.

The reference hardware/display contract now keeps local and RDP populations separate and defines the screenshot and re-baseline rules needed by later prepared-content gates.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-24 12:46:51 +02:00
parent bf7ec12f68
commit 3ee8ec537a
23 changed files with 1639 additions and 170 deletions

View file

@ -5,6 +5,9 @@ param(
[string]$Password = $env:ACDREAM_TEST_PASS,
[switch]$SkipBuild,
[switch]$Uncapped,
[switch]$DenseTown,
[switch]$CaptureContention,
[switch]$SkipRuntimeCounters,
[int]$LoginTimeoutSeconds = 90
)
@ -14,33 +17,45 @@ $ErrorActionPreference = 'Stop'
if ([string]::IsNullOrWhiteSpace($Account)) { $Account = 'testaccount' }
if ([string]::IsNullOrWhiteSpace($Password)) { $Password = 'testpassword' }
$destinations = @(
[pscustomobject]@{ Name = 'Caul'; Occurrence = 1; Exercise = $true },
[pscustomobject]@{ Name = 'Sawato'; Occurrence = 2; Exercise = $false },
[pscustomobject]@{ Name = 'Rynthid'; Occurrence = 3; Exercise = $false },
[pscustomobject]@{ Name = 'Aerlinthe'; Occurrence = 4; Exercise = $false },
[pscustomobject]@{ Name = 'Sawato revisit'; Occurrence = 5; Exercise = $false },
[pscustomobject]@{ Name = 'Holtburg'; Occurrence = 6; Exercise = $true },
[pscustomobject]@{ Name = 'Caul return'; Occurrence = 7; Exercise = $true },
[pscustomobject]@{ Name = 'Sawato plateau'; Occurrence = 8; Exercise = $false },
[pscustomobject]@{ Name = 'Caul plateau'; Occurrence = 9; Exercise = $false }
)
$expectedCheckpointNames = @(
'caul-baseline',
'sawato-baseline',
'rynthid',
'aerlinthe',
'sawato-return',
'holtburg',
'caul-return',
'sawato-plateau',
'caul-plateau'
)
if ($DenseTown) {
[object[]]$destinations = @(
[pscustomobject]@{ Name = 'Arwic dense'; Occurrence = 1; Exercise = $false }
)
[string[]]$expectedCheckpointNames = @('arwic-dense')
$routeFileName = 'connected-dense-town.route.txt'
$runName = 'connected-dense-town'
}
else {
[object[]]$destinations = @(
[pscustomobject]@{ Name = 'Caul'; Occurrence = 1; Exercise = $true },
[pscustomobject]@{ Name = 'Sawato'; Occurrence = 2; Exercise = $false },
[pscustomobject]@{ Name = 'Rynthid'; Occurrence = 3; Exercise = $false },
[pscustomobject]@{ Name = 'Aerlinthe'; Occurrence = 4; Exercise = $false },
[pscustomobject]@{ Name = 'Sawato revisit'; Occurrence = 5; Exercise = $false },
[pscustomobject]@{ Name = 'Holtburg'; Occurrence = 6; Exercise = $true },
[pscustomobject]@{ Name = 'Caul return'; Occurrence = 7; Exercise = $true },
[pscustomobject]@{ Name = 'Sawato plateau'; Occurrence = 8; Exercise = $false },
[pscustomobject]@{ Name = 'Caul plateau'; Occurrence = 9; Exercise = $false }
)
[string[]]$expectedCheckpointNames = @(
'caul-baseline',
'sawato-baseline',
'rynthid',
'aerlinthe',
'sawato-return',
'holtburg',
'caul-return',
'sawato-plateau',
'caul-plateau'
)
$routeFileName = 'connected-r6-soak.route.txt'
$runName = 'connected-r6-soak'
}
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$logDir = Join-Path $Repository 'logs'
$null = New-Item -ItemType Directory -Force -Path $logDir
$prefix = Join-Path $logDir "connected-r6-soak-$stamp"
$prefix = Join-Path $logDir "$runName-$stamp"
$stdoutLog = "$prefix.out.log"
$stderrLog = "$prefix.err.log"
$markerLog = "$prefix.markers.log"
@ -48,13 +63,24 @@ $sampleCsv = "$prefix.samples.csv"
$reportJson = "$prefix.report.json"
$artifactDir = "$prefix.artifacts"
$checkpointTimeline = Join-Path $artifactDir 'world-lifecycle.checkpoints.jsonl'
$routePath = Join-Path $Repository 'tools\connected-r6-soak.route.txt'
$frameHistory = Join-Path $artifactDir 'frame-history.csv'
$frameSummary = Join-Path $artifactDir 'frame-history-summary.json'
$contentionTrace = Join-Path $artifactDir 'contention.nettrace'
$contentionStdout = Join-Path $artifactDir 'contention.out.log'
$contentionStderr = Join-Path $artifactDir 'contention.err.log'
$runtimeCounters = Join-Path $artifactDir 'runtime-counters.csv'
$counterStdout = Join-Path $artifactDir 'runtime-counters.out.log'
$counterStderr = Join-Path $artifactDir 'runtime-counters.err.log'
$routePath = Join-Path $Repository "tools\$routeFileName"
$exe = Join-Path $Repository 'src\AcDream.App\bin\Release\net10.0\AcDream.App.exe'
$cliDll = Join-Path $Repository 'src\AcDream.Cli\bin\Release\net10.0\AcDream.Cli.dll'
$samples = [System.Collections.Generic.List[object]]::new()
$failures = [System.Collections.Generic.List[string]]::new()
$warnings = [System.Collections.Generic.List[string]]::new()
$process = $null
$traceProcess = $null
$counterProcess = $null
$stopwatch = [Diagnostics.Stopwatch]::StartNew()
$gracefulExit = $false
$exitCode = $null
@ -374,6 +400,10 @@ function Add-CanonicalCheckpointFailures([Diagnostics.Process]$Client) {
}
}
if ($DenseTown) {
return
}
$caulReturn = $canonicalCheckpoints |
Where-Object { $_.name -eq 'caul-return' } |
Select-Object -First 1
@ -588,6 +618,7 @@ if (-not $SkipBuild) {
if ($LASTEXITCODE -ne 0) { throw "Release build failed with exit code $LASTEXITCODE" }
}
if (-not (Test-Path -LiteralPath $exe)) { throw "client executable not found: $exe" }
if (-not (Test-Path -LiteralPath $cliDll)) { throw "CLI assembly not found: $cliDll" }
$commit = (& git -C $Repository rev-parse HEAD).Trim()
$sourceStatus = @(& git -C $Repository status --short)
@ -603,6 +634,7 @@ $env:ACDREAM_TEST_USER = $Account
$env:ACDREAM_TEST_PASS = $Password
$env:ACDREAM_RETAIL_UI = '1'
$env:ACDREAM_FRAME_PROF = '1'
$env:ACDREAM_FRAME_HISTORY = $frameHistory
# Capped by default (matches the retail presentation cadence the connected
# visual matrix is gated against); pass -Uncapped to opt into uncapped
# render, matching run-connected-world-lifecycle-gate.ps1's pattern.
@ -623,11 +655,21 @@ $env:ACDREAM_WB_DIAG = $null
$null = New-Item -ItemType Directory -Force -Path $artifactDir
$acdreamEnvVars = Get-ChildItem Env: | Where-Object { $_.Name -like 'ACDREAM_*' } |
Sort-Object Name |
ForEach-Object { [pscustomobject]@{ Name = $_.Name; Value = $_.Value } }
ForEach-Object {
$sensitive = $_.Name -match '(?i)(PASS|PASSWORD|TOKEN|SECRET|KEY)'
[pscustomobject]@{
Name = $_.Name
Value = if ($sensitive) { '<redacted>' } else { $_.Value }
}
}
$envDisclosure = [pscustomobject][ordered]@{
Script = 'run-connected-r6-soak.ps1'
GeneratedUtc = [DateTime]::UtcNow.ToString('O')
Uncapped = [bool]$Uncapped
DenseTown = [bool]$DenseTown
CaptureContention = [bool]$CaptureContention
RuntimeCounters = -not [bool]$SkipRuntimeCounters
Route = $routeFileName
EnvironmentVariables = @($acdreamEnvVars)
}
$envDisclosure | ConvertTo-Json -Depth 4 |
@ -638,6 +680,42 @@ try {
-RedirectStandardOutput $stdoutLog -RedirectStandardError $stderrLog -PassThru
Write-Marker "launch pid=$($process.Id) commit=$commit session='$env:SESSIONNAME'"
if (-not $SkipRuntimeCounters) {
$counterArguments = @(
'collect',
'--process-id', $process.Id,
'--counters', 'System.Runtime',
'--refresh-interval', '1',
'--format', 'csv',
'--output', $runtimeCounters,
'--duration', '00:00:15:00'
)
$counterProcess = Start-Process -FilePath 'dotnet-counters' `
-ArgumentList $counterArguments `
-RedirectStandardOutput $counterStdout `
-RedirectStandardError $counterStderr `
-PassThru `
-WindowStyle Hidden
Write-Marker "runtime-counters pid=$($counterProcess.Id)"
}
if ($CaptureContention) {
$traceArguments = @(
'collect',
'--process-id', $process.Id,
'--providers', 'Microsoft-Windows-DotNETRuntime:0x4000:5',
'--output', $contentionTrace,
'--duration', '00:00:15:00'
)
$traceProcess = Start-Process -FilePath 'dotnet-trace' `
-ArgumentList $traceArguments `
-RedirectStandardOutput $contentionStdout `
-RedirectStandardError $contentionStderr `
-PassThru `
-WindowStyle Hidden
Write-Marker "contention-capture pid=$($traceProcess.Id)"
}
$null = Wait-ForLogPattern $process $stdoutLog 'live: in world' 1 $LoginTimeoutSeconds
$null = Wait-ForLogPattern $process $stdoutLog 'live: first player position' 1 $LoginTimeoutSeconds
Write-Marker ("in-world elapsed={0:N1}s" -f $stopwatch.Elapsed.TotalSeconds)
@ -681,15 +759,19 @@ try {
$failures.Add("expected $($destinations.Count) teleport materializations")
}
$lastCheckpointName = $expectedCheckpointNames[-1]
$lastCheckpointSequence = $expectedCheckpointNames.Count
$null = Wait-ForLogPattern `
$process `
$stdoutLog `
'[world-gate] checkpoint name=caul-plateau sequence=9' `
"[world-gate] checkpoint name=$lastCheckpointName sequence=$lastCheckpointSequence" `
1 `
30
$canonicalCheckpoints = @(Read-CanonicalCheckpoints)
Add-CanonicalCheckpointFailures $process
Add-RelativeGateFailures
if (-not $DenseTown) {
Add-RelativeGateFailures
}
Write-Marker 'route-complete requesting graceful close'
$gracefulExit = Close-ClientGracefully $process
@ -717,6 +799,46 @@ finally {
if ($process.HasExited -and $null -eq $exitCode) { $exitCode = [int]$process.ExitCode }
$process.Dispose()
}
if ($null -ne $traceProcess) {
if (-not $traceProcess.WaitForExit(30000)) {
$warnings.Add('dotnet-trace did not exit within 30 seconds of client shutdown')
Stop-Process -Id $traceProcess.Id -Force
$traceProcess.WaitForExit(10000)
}
if (-not (Test-Path -LiteralPath $contentionTrace)) {
$failures.Add('contention capture did not produce a nettrace artifact')
}
$traceProcess.Dispose()
}
if ($null -ne $counterProcess) {
if (-not $counterProcess.WaitForExit(30000)) {
$warnings.Add('dotnet-counters did not exit within 30 seconds of client shutdown')
Stop-Process -Id $counterProcess.Id -Force
$counterProcess.WaitForExit(10000)
}
if (-not (Test-Path -LiteralPath $runtimeCounters)) {
$failures.Add('runtime counter capture did not produce a CSV artifact')
}
$counterProcess.Dispose()
}
if (-not (Test-Path -LiteralPath $frameHistory)) {
$failures.Add('frame-history export was not written during graceful shutdown')
}
elseif ((Test-Path -LiteralPath $checkpointTimeline) -and
(Test-Path -LiteralPath $markerLog)) {
& dotnet $cliDll summarize-frame-history `
$frameHistory `
$checkpointTimeline `
$markerLog `
$frameSummary
if ($LASTEXITCODE -ne 0) {
$failures.Add("frame-history summary failed with exit code $LASTEXITCODE")
}
elseif (-not (Test-Path -LiteralPath $frameSummary)) {
$failures.Add('frame-history summary did not produce its JSON artifact')
}
}
$samples | Export-Csv -LiteralPath $sampleCsv -NoTypeInformation
$report = [pscustomobject][ordered]@{
@ -742,6 +864,10 @@ finally {
ReportJson = $reportJson
CheckpointTimeline = $checkpointTimeline
EnvDisclosure = Join-Path $artifactDir 'env-disclosure.json'
FrameHistory = $frameHistory
FrameSummary = $frameSummary
ContentionTrace = if ($CaptureContention) { $contentionTrace } else { $null }
RuntimeCounters = if (-not $SkipRuntimeCounters) { $runtimeCounters } else { $null }
}
}
$report | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $reportJson -Encoding utf8