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:
parent
bf7ec12f68
commit
3ee8ec537a
23 changed files with 1639 additions and 170 deletions
19
tools/connected-dense-town.route.txt
Normal file
19
tools/connected-dense-town.route.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Deterministic dense-town performance scene. Arwic is the established
|
||||
# dense-town oracle used by the June and July renderer investigations.
|
||||
# /telepoi supplies ACE's canonical arrival position and heading. The fixed
|
||||
# six-second retail-input turn pins the view facing for every capture.
|
||||
|
||||
sleep 15000
|
||||
# Pin the client-only world-time override at noon for reproducible screenshots.
|
||||
input press AcdreamCycleTimeOfDay
|
||||
input press AcdreamCycleTimeOfDay
|
||||
input press AcdreamCycleTimeOfDay
|
||||
command /telepoi Arwic
|
||||
wait materialized 1 60000
|
||||
sleep 8000
|
||||
input down MovementTurnRight
|
||||
sleep 6000
|
||||
input up MovementTurnRight
|
||||
sleep 26000
|
||||
screenshot arwic-dense 10000
|
||||
checkpoint arwic-dense
|
||||
|
|
@ -3,6 +3,11 @@
|
|||
# remains deterministic when no RDP/interactive desktop is attached.
|
||||
|
||||
sleep 15000
|
||||
# Pin the client-only world-time override at noon for reproducible screenshots.
|
||||
# The diagnostic action cycles: live -> 0.00 -> 0.25 -> 0.50.
|
||||
input press AcdreamCycleTimeOfDay
|
||||
input press AcdreamCycleTimeOfDay
|
||||
input press AcdreamCycleTimeOfDay
|
||||
|
||||
# Caul baseline + local movement/jump/combat exercise. Every teleloc carries
|
||||
# the identity quaternion so each timed turn begins from the same heading.
|
||||
|
|
@ -41,6 +46,7 @@ input up MovementForward
|
|||
sleep 700
|
||||
input press CombatToggleCombat
|
||||
sleep 7400
|
||||
screenshot caul-baseline 10000
|
||||
checkpoint caul-baseline
|
||||
|
||||
# Sawato baseline.
|
||||
|
|
@ -51,6 +57,7 @@ input down MovementTurnRight
|
|||
sleep 6000
|
||||
input up MovementTurnRight
|
||||
sleep 22000
|
||||
screenshot sawato-baseline 10000
|
||||
checkpoint sawato-baseline
|
||||
|
||||
# Rynthid world-edge streaming.
|
||||
|
|
@ -61,6 +68,7 @@ input down MovementTurnRight
|
|||
sleep 6000
|
||||
input up MovementTurnRight
|
||||
sleep 22000
|
||||
screenshot rynthid 10000
|
||||
checkpoint rynthid
|
||||
|
||||
# Aerlinthe dense island.
|
||||
|
|
@ -71,6 +79,7 @@ input down MovementTurnRight
|
|||
sleep 6000
|
||||
input up MovementTurnRight
|
||||
sleep 22000
|
||||
screenshot aerlinthe 10000
|
||||
checkpoint aerlinthe
|
||||
|
||||
# Same-location lifecycle revisit.
|
||||
|
|
@ -81,6 +90,7 @@ input down MovementTurnRight
|
|||
sleep 6000
|
||||
input up MovementTurnRight
|
||||
sleep 22000
|
||||
screenshot sawato-return 10000
|
||||
checkpoint sawato-return
|
||||
|
||||
# Holtburg mixed-town workload + local exercise.
|
||||
|
|
@ -119,6 +129,7 @@ input up MovementForward
|
|||
sleep 700
|
||||
input press CombatToggleCombat
|
||||
sleep 7400
|
||||
screenshot holtburg 10000
|
||||
checkpoint holtburg
|
||||
|
||||
# Caul return/leak oracle + final local exercise.
|
||||
|
|
@ -157,6 +168,7 @@ input up MovementForward
|
|||
sleep 700
|
||||
input press CombatToggleCombat
|
||||
sleep 15000
|
||||
screenshot caul-return 10000
|
||||
checkpoint caul-return
|
||||
|
||||
# A second warm-cache Sawato/Caul revisit is the plateau oracle. The first
|
||||
|
|
@ -170,6 +182,7 @@ input down MovementTurnRight
|
|||
sleep 6000
|
||||
input up MovementTurnRight
|
||||
sleep 22000
|
||||
screenshot sawato-plateau 10000
|
||||
checkpoint sawato-plateau
|
||||
|
||||
command /teleloc 0xC95B0001 14.8 0.3 12.005 1 0 0 0
|
||||
|
|
@ -179,4 +192,5 @@ input down MovementTurnRight
|
|||
sleep 6000
|
||||
input up MovementTurnRight
|
||||
sleep 22000
|
||||
screenshot caul-plateau 10000
|
||||
checkpoint caul-plateau
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue