Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
145 lines
7.1 KiB
PowerShell
145 lines
7.1 KiB
PowerShell
param(
|
|
[Parameter(Mandatory)][string]$OutputDirectory,
|
|
[ValidateRange(5,900)][int]$Seconds=60,
|
|
[string]$ProbeExecutable,
|
|
[string[]]$ProbeArguments=@(),
|
|
[ValidateRange(0,9223372036854775807)][long]$ProbeAffinityMask=0
|
|
)
|
|
# Runs an owned, uniquely named ETW session. Does not launch or stop Dolphin.
|
|
# Native use requires a separately launched, frozen diagnostic build and manifest.
|
|
$ErrorActionPreference = 'Stop'
|
|
function ConvertTo-P1WindowsArgument([string]$Value) {
|
|
# Windows PowerShell 5.1 uses .NET Framework, where ProcessStartInfo has no
|
|
# ArgumentList. Quote paths using the standard Windows argv backslash rules.
|
|
$p1Quoted = '"'
|
|
$p1Slashes = 0
|
|
foreach ($p1Char in $Value.ToCharArray()) {
|
|
if ($p1Char -eq '\') { ++$p1Slashes; continue }
|
|
if ($p1Char -eq '"') {
|
|
$p1Quoted += ('\' * (2 * $p1Slashes + 1)) + '"'
|
|
$p1Slashes = 0
|
|
continue
|
|
}
|
|
$p1Quoted += ('\' * $p1Slashes) + $p1Char
|
|
$p1Slashes = 0
|
|
}
|
|
return $p1Quoted + ('\' * (2 * $p1Slashes)) + '"'
|
|
}
|
|
$p1Out = [IO.Path]::GetFullPath($(if ([IO.Path]::IsPathRooted($OutputDirectory)) {
|
|
$OutputDirectory
|
|
} else {
|
|
Join-Path (Get-Location).ProviderPath $OutputDirectory
|
|
}))
|
|
if (Test-Path -LiteralPath $p1Out) { throw 'Use a NEW capture directory; results are never overwritten.' }
|
|
if ($ProbeExecutable) {
|
|
$ProbeExecutable = (Resolve-Path -LiteralPath $ProbeExecutable).Path
|
|
if ([IO.Path]::GetFileName($ProbeExecutable) -ne 'TraceProbeP1.exe') {
|
|
throw 'Automatic launch is restricted to the local qualification probe, not Dolphin.'
|
|
}
|
|
}
|
|
if ($ProbeAffinityMask -ne 0) {
|
|
$p1Allowed = [Diagnostics.Process]::GetCurrentProcess().ProcessorAffinity.ToInt64()
|
|
if (($ProbeAffinityMask -band $p1Allowed) -ne $ProbeAffinityMask) {
|
|
throw "Probe affinity mask $ProbeAffinityMask is not available to this process."
|
|
}
|
|
}
|
|
$p1Drive = [IO.DriveInfo]::new([IO.Path]::GetPathRoot($p1Out))
|
|
if ($p1Drive.AvailableFreeSpace -lt 9GB) { throw 'Need 4 GiB trace budget plus 5 GiB reserve.' }
|
|
New-Item -ItemType Directory -Path $p1Out | Out-Null
|
|
$p1Name = 'DolphinP1-' + [guid]::NewGuid().ToString('N')
|
|
$p1Temp = Join-Path (Split-Path $p1Out -Parent) ($p1Name + '-temporary-etl')
|
|
New-Item -ItemType Directory -Path $p1Temp | Out-Null
|
|
$p1Profile = Join-Path $PSScriptRoot 'DolphinP1.wprp'
|
|
$p1ETL = Join-Path $p1Out 'capture.etl'
|
|
$p1PDB = if ($ProbeExecutable) {
|
|
Join-Path (Split-Path (Split-Path (Split-Path $ProbeExecutable -Parent) -Parent) -Parent) 'pdb/TraceProbeP1.pdb'
|
|
} else { $null }
|
|
$p1Started = $false
|
|
$p1Reason = 'duration'
|
|
$p1Probe = $null
|
|
$p1Stdout = $null
|
|
$p1Stderr = $null
|
|
$p1Manifest = [ordered]@{
|
|
Schema=1; Collection='WPRSystemAndNative'; Session=$p1Name
|
|
StartUTC=[datetime]::UtcNow.ToString('o')
|
|
Seconds=$Seconds; TemporaryTraceDirectory=$p1Temp
|
|
Profile=$p1Profile; ProfileSHA256=(Get-FileHash -LiteralPath $p1Profile).Hash
|
|
Probe=$ProbeExecutable; ProbeSHA256=$(if ($ProbeExecutable) { (Get-FileHash -LiteralPath $ProbeExecutable).Hash } else { $null })
|
|
ProbeAffinityMask=$ProbeAffinityMask
|
|
ProbePDB=$p1PDB; ProbePDBSHA256=$(if ($p1PDB -and (Test-Path -LiteralPath $p1PDB)) { (Get-FileHash -LiteralPath $p1PDB).Hash } else { $null })
|
|
NativeQualificationPassed=$false
|
|
}
|
|
$p1Manifest | ConvertTo-Json -Depth 5 | Out-File -LiteralPath (Join-Path $p1Out 'manifest.json') -Encoding utf8
|
|
try {
|
|
& wpr.exe -start ($p1Profile + '!DolphinP1') -filemode -recordtempto $p1Temp -instancename $p1Name *> (Join-Path $p1Out 'start.log')
|
|
if ($LASTEXITCODE) { throw "ETW start failed; see $p1Out\start.log. No other session was stopped." }
|
|
$p1Started = $true
|
|
if (-not $ProbeExecutable) {
|
|
Write-Output "Native collector armed for $Seconds seconds: $p1Name. Launch Dolphin separately with -PerformanceTrace now."
|
|
}
|
|
if ($ProbeExecutable) {
|
|
# Environment of this child only; never persistently modify the user's environment.
|
|
$p1Info = [Diagnostics.ProcessStartInfo]::new($ProbeExecutable)
|
|
$p1Info.UseShellExecute = $false
|
|
$p1Info.CreateNoWindow = $true
|
|
$p1Info.RedirectStandardOutput = $true
|
|
$p1Info.RedirectStandardError = $true
|
|
$p1Info.Environment['DOLPHIN_PERF_TRACE'] = '1'
|
|
if ($p1Info.PSObject.Properties['ArgumentList'] -and $p1Info.ArgumentList) {
|
|
foreach ($p1Arg in $ProbeArguments) { $p1Info.ArgumentList.Add($p1Arg) }
|
|
} else {
|
|
$p1Info.Arguments = ($ProbeArguments | ForEach-Object {
|
|
ConvertTo-P1WindowsArgument $_
|
|
}) -join ' '
|
|
}
|
|
$p1Probe = [Diagnostics.Process]::Start($p1Info)
|
|
if ($ProbeAffinityMask -ne 0) {
|
|
try {
|
|
$p1Probe.ProcessorAffinity = [IntPtr]$ProbeAffinityMask
|
|
} catch {
|
|
$p1Probe.Kill() # Only the synthetic probe started by this script.
|
|
$p1Probe.WaitForExit()
|
|
throw "Could not pin owned synthetic probe to mask ${ProbeAffinityMask}: $_"
|
|
}
|
|
}
|
|
$p1Manifest.ProbeObservedAffinityMask = $p1Probe.ProcessorAffinity.ToInt64()
|
|
$p1Stdout = $p1Probe.StandardOutput.ReadToEndAsync()
|
|
$p1Stderr = $p1Probe.StandardError.ReadToEndAsync()
|
|
$p1Manifest.ProbePID = $p1Probe.Id
|
|
$p1Manifest.ProbeStartUTC = $p1Probe.StartTime.ToUniversalTime().ToString('o')
|
|
}
|
|
$p1Clock = [Diagnostics.Stopwatch]::StartNew()
|
|
while ($p1Clock.Elapsed.TotalSeconds -lt $Seconds) {
|
|
Start-Sleep -Milliseconds 1000
|
|
$p1Bytes = (Get-ChildItem -LiteralPath $p1Temp -File -Recurse | Measure-Object Length -Sum).Sum
|
|
if ($p1Drive.AvailableFreeSpace -lt 5GB -or $p1Bytes -gt 4GB) { $p1Reason='storage-budget'; break }
|
|
if ($p1Probe -and $p1Probe.HasExited) { $p1Reason='probe-exit'; break }
|
|
}
|
|
} finally {
|
|
if ($p1Started) {
|
|
& wpr.exe -status collectors -details -instancename $p1Name *> (Join-Path $p1Out 'collector-status.log')
|
|
& wpr.exe -stop $p1ETL -instancename $p1Name *> (Join-Path $p1Out 'stop.log')
|
|
$p1StopExit = $LASTEXITCODE
|
|
$p1Manifest.EndUTC = [datetime]::UtcNow.ToString('o')
|
|
$p1Manifest.StopReason = $p1Reason
|
|
$p1Manifest.StopExitCode = $p1StopExit
|
|
$p1Manifest.ProbeExitCode = if ($p1Probe -and $p1Probe.HasExited) { $p1Probe.ExitCode } else { $null }
|
|
if ($p1Probe -and $p1Probe.HasExited) {
|
|
$p1Stdout.GetAwaiter().GetResult() | Out-File -LiteralPath (Join-Path $p1Out 'probe.stdout.log') -Encoding utf8
|
|
$p1Stderr.GetAwaiter().GetResult() | Out-File -LiteralPath (Join-Path $p1Out 'probe.stderr.log') -Encoding utf8
|
|
$p1Manifest.ProbeCPUSeconds = $p1Probe.TotalProcessorTime.TotalSeconds
|
|
$p1Manifest.ProbeWallSeconds = ($p1Probe.ExitTime - $p1Probe.StartTime).TotalSeconds
|
|
}
|
|
$p1Manifest | ConvertTo-Json -Depth 5 | Out-File -LiteralPath (Join-Path $p1Out 'manifest.json') -Encoding utf8
|
|
if ($p1StopExit) { throw 'Owned ETW session could not be saved. See stop.log; no broad cancel attempted.' }
|
|
}
|
|
}
|
|
if (-not (Test-Path -LiteralPath $p1ETL)) { throw 'WPR reported success but produced no capture.etl.' }
|
|
if ($p1Probe -and -not $p1Probe.HasExited) {
|
|
throw "Capture stopped at $p1Reason, but the owned local probe is still running (PID $($p1Probe.Id)). No process was forcibly stopped."
|
|
}
|
|
if ($p1Probe -and $p1Probe.ExitCode -ne 0) {
|
|
throw "Local probe failed with exit code $($p1Probe.ExitCode); see probe.stderr.log. The ETL was kept."
|
|
}
|
|
Write-Output "Saved $p1ETL. Qualification is NOT automatically passed; inspect losses, attribution and overhead."
|