[CmdletBinding()]
|
param(
|
[Parameter(Mandatory = $true)]
|
[string]$PythonPath,
|
|
[Parameter(Mandatory = $true)]
|
[string]$ScriptPath,
|
|
[Parameter(Mandatory = $true)]
|
[string]$VideoPath,
|
|
[Parameter(Mandatory = $true)]
|
[string]$OutputPath,
|
|
[Parameter(Mandatory = $true)]
|
[string]$EvidenceDirectory,
|
|
[string]$Language,
|
[string]$OriginalSourcePath,
|
[string]$OriginalSourceBaselinePath,
|
[int]$ExpectedSegments = 0,
|
[switch]$SkipValidation
|
)
|
|
Set-StrictMode -Version Latest
|
$ErrorActionPreference = "Stop"
|
$SampleIntervalMilliseconds = 500
|
$RamLimitBytes = 12GB
|
$GpuLimitMiB = 10 * 1024
|
$RamGrowthLimitBytes = 2GB
|
$TempChunkLimitBytes = 500MB
|
|
function Resolve-RequiredPath([string]$Path, [string]$Label) {
|
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
throw "$Label does not exist: $Path"
|
}
|
return (Resolve-Path -LiteralPath $Path).Path
|
}
|
|
function Resolve-FuturePath([string]$Path) {
|
$full = [System.IO.Path]::GetFullPath($Path)
|
return $full
|
}
|
|
function Quote-ProcessArgument([string]$Value) {
|
if ($Value.Length -eq 0) {
|
return '""'
|
}
|
if ($Value -notmatch '[\s"]') {
|
return $Value
|
}
|
return '"' + ($Value -replace '(\\*)"', '$1$1\"' -replace '(\\+)$', '$1$1') + '"'
|
}
|
|
function Write-JsonFile([string]$Path, $Value) {
|
$json = $Value | ConvertTo-Json -Depth 20
|
[System.IO.File]::WriteAllText($Path, $json + [Environment]::NewLine, (New-Object System.Text.UTF8Encoding($false)))
|
}
|
|
function Get-Maximum($Values) {
|
$items = @($Values)
|
if ($items.Count -eq 0) {
|
return 0
|
}
|
return [double](($items | Measure-Object -Maximum).Maximum)
|
}
|
|
$PythonPath = Resolve-RequiredPath $PythonPath "Python"
|
$ScriptPath = Resolve-RequiredPath $ScriptPath "transcription script"
|
$VideoPath = Resolve-RequiredPath $VideoPath "test video"
|
if ([string]::IsNullOrWhiteSpace($OriginalSourcePath)) {
|
$OriginalSourcePath = $VideoPath
|
}
|
$OriginalSourcePath = Resolve-RequiredPath $OriginalSourcePath "original source video"
|
$OutputPath = Resolve-FuturePath $OutputPath
|
$EvidenceDirectory = Resolve-FuturePath $EvidenceDirectory
|
$ValidatorPath = Resolve-RequiredPath (Join-Path $PSScriptRoot "validate_transcription_acceptance.py") "acceptance validator"
|
|
if (Test-Path -LiteralPath $EvidenceDirectory) {
|
throw "Evidence directory already exists; refusing to overwrite: $EvidenceDirectory"
|
}
|
[void](New-Item -ItemType Directory -Path $EvidenceDirectory)
|
|
$SourceBeforePath = Join-Path $EvidenceDirectory "source_before.json"
|
if (-not ([string]::IsNullOrWhiteSpace($OriginalSourceBaselinePath))) {
|
$OriginalSourceBaselinePath = Resolve-RequiredPath $OriginalSourceBaselinePath "source baseline"
|
Copy-Item -LiteralPath $OriginalSourceBaselinePath -Destination $SourceBeforePath
|
} else {
|
$SnapshotArguments = @("-X", "utf8", "-B", $ValidatorPath, "snapshot", "--source", $OriginalSourcePath, "--output", $SourceBeforePath)
|
& $PythonPath @SnapshotArguments
|
if ($LASTEXITCODE -ne 0) {
|
throw "Could not capture the source fingerprint."
|
}
|
}
|
|
$StdoutPath = Join-Path $EvidenceDirectory "stdout.log"
|
$StderrPath = Join-Path $EvidenceDirectory "stderr.log"
|
$SamplesPath = Join-Path $EvidenceDirectory "resource_samples.csv"
|
$BlockPeaksPath = Join-Path $EvidenceDirectory "resource_block_peaks.csv"
|
$SummaryPath = Join-Path $EvidenceDirectory "resource_summary.json"
|
$CommandPath = Join-Path $EvidenceDirectory "command.json"
|
$AcceptancePath = Join-Path $EvidenceDirectory "acceptance_evidence.json"
|
[System.IO.File]::WriteAllText($StdoutPath, [string]::Empty, (New-Object System.Text.UTF8Encoding($false)))
|
[System.IO.File]::WriteAllText($StderrPath, [string]::Empty, (New-Object System.Text.UTF8Encoding($false)))
|
|
$Arguments = New-Object System.Collections.Generic.List[string]
|
$Arguments.Add("-X")
|
$Arguments.Add("utf8")
|
$Arguments.Add("-u")
|
$Arguments.Add("-B")
|
$Arguments.Add($ScriptPath)
|
$Arguments.Add($VideoPath)
|
$Arguments.Add("--output")
|
$Arguments.Add($OutputPath)
|
if (-not ([string]::IsNullOrWhiteSpace($Language))) {
|
$Arguments.Add("--language")
|
$Arguments.Add($Language)
|
}
|
$ArgumentString = (($Arguments | ForEach-Object { Quote-ProcessArgument $_ }) -join " ")
|
$GpuPerformanceCategory = New-Object System.Diagnostics.PerformanceCounterCategory("GPU Process Memory")
|
[void]$GpuPerformanceCategory.GetInstanceNames()
|
|
$StartTimeUtc = [DateTime]::UtcNow
|
$StdoutQueue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[string]'
|
$StderrQueue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[string]'
|
$ProcessStartInfo = New-Object System.Diagnostics.ProcessStartInfo
|
$ProcessStartInfo.FileName = $PythonPath
|
$ProcessStartInfo.Arguments = $ArgumentString
|
$ProcessStartInfo.WorkingDirectory = (Get-Location).Path
|
$ProcessStartInfo.UseShellExecute = $false
|
$ProcessStartInfo.CreateNoWindow = $true
|
$ProcessStartInfo.RedirectStandardOutput = $true
|
$ProcessStartInfo.RedirectStandardError = $true
|
$ProcessStartInfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8
|
$ProcessStartInfo.StandardErrorEncoding = [System.Text.Encoding]::UTF8
|
$Process = New-Object System.Diagnostics.Process
|
$Process.StartInfo = $ProcessStartInfo
|
$StdoutSubscription = Register-ObjectEvent -InputObject $Process -EventName OutputDataReceived -MessageData $StdoutQueue -Action {
|
if ($null -ne $event.SourceEventArgs.Data) {
|
$event.MessageData.Enqueue($event.SourceEventArgs.Data)
|
}
|
}
|
$StderrSubscription = Register-ObjectEvent -InputObject $Process -EventName ErrorDataReceived -MessageData $StderrQueue -Action {
|
if ($null -ne $event.SourceEventArgs.Data) {
|
$event.MessageData.Enqueue($event.SourceEventArgs.Data)
|
}
|
}
|
[void]$Process.Start()
|
$Process.BeginOutputReadLine()
|
$Process.BeginErrorReadLine()
|
$RootPid = [int]$Process.Id
|
$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
$Samples = New-Object System.Collections.Generic.List[object]
|
$SamplingErrors = New-Object System.Collections.Generic.List[string]
|
$CurrentBlock = $null
|
$BlockTotal = $null
|
$FfmpegSeen = $false
|
$NvidiaRootPidSeen = $false
|
$GpuCounterTaskInstanceSeen = $false
|
$GpuDedicatedUsageNonzeroSeen = $false
|
$LastScheduledMilliseconds = 0L
|
$MediaStem = [System.IO.Path]::GetFileNameWithoutExtension($VideoPath)
|
$OutputParent = Split-Path -Parent $OutputPath
|
$StagingPrefix = ".$MediaStem.transcribe-"
|
|
do {
|
$SampleStartedMilliseconds = $Stopwatch.ElapsedMilliseconds
|
try {
|
$CapturedLine = $null
|
while ($StdoutQueue.TryDequeue([ref]$CapturedLine)) {
|
[System.IO.File]::AppendAllText($StdoutPath, $CapturedLine + [Environment]::NewLine, (New-Object System.Text.UTF8Encoding($false)))
|
$Progress = [regex]::Match($CapturedLine, '\u6b63\u5728\u8f6c\u5199\u5206\u5757\s+(\d+)/(\d+)')
|
if ($Progress.Success) {
|
$CurrentBlock = [int]$Progress.Groups[1].Value
|
$BlockTotal = [int]$Progress.Groups[2].Value
|
}
|
$CapturedLine = $null
|
}
|
while ($StderrQueue.TryDequeue([ref]$CapturedLine)) {
|
[System.IO.File]::AppendAllText($StderrPath, $CapturedLine + [Environment]::NewLine, (New-Object System.Text.UTF8Encoding($false)))
|
$CapturedLine = $null
|
}
|
|
$ProcessSnapshot = @(Get-CimInstance Win32_Process -Property ProcessId, ParentProcessId, Name, WorkingSetSize)
|
$TaskPids = New-Object 'System.Collections.Generic.HashSet[int]'
|
[void]$TaskPids.Add($RootPid)
|
do {
|
$Added = $false
|
foreach ($Entry in $ProcessSnapshot) {
|
$EntryProcessId = [int]$Entry.ProcessId
|
$ParentPid = [int]$Entry.ParentProcessId
|
if ($TaskPids.Contains($ParentPid) -and -not $TaskPids.Contains($EntryProcessId)) {
|
[void]$TaskPids.Add($EntryProcessId)
|
$Added = $true
|
}
|
}
|
} while ($Added)
|
|
$RootEntry = $ProcessSnapshot | Where-Object { [int]$_.ProcessId -eq $RootPid } | Select-Object -First 1
|
$PythonWorkingSet = if ($null -eq $RootEntry) { 0L } else { [int64]$RootEntry.WorkingSetSize }
|
$Descendants = @($ProcessSnapshot | Where-Object { [int]$_.ProcessId -ne $RootPid -and $TaskPids.Contains([int]$_.ProcessId) })
|
$FfmpegProcesses = @($Descendants | Where-Object { $_.Name -ieq "ffmpeg.exe" })
|
if ($FfmpegProcesses.Count -gt 0) {
|
$FfmpegSeen = $true
|
}
|
$FfmpegWorkingSet = if ($FfmpegProcesses.Count -eq 0) {
|
0L
|
} else {
|
[int64](($FfmpegProcesses | Measure-Object -Property WorkingSetSize -Sum).Sum)
|
}
|
$CombinedWorkingSet = $PythonWorkingSet + $FfmpegWorkingSet
|
$DescendantEvidence = @($Descendants | ForEach-Object {
|
[ordered]@{
|
pid = [int]$_.ProcessId
|
parent_pid = [int]$_.ParentProcessId
|
name = [string]$_.Name
|
}
|
}) | ConvertTo-Json -Compress
|
|
$NvidiaTaskProcesses = New-Object System.Collections.Generic.List[object]
|
$GpuRows = @(& nvidia-smi --query-compute-apps=pid,used_gpu_memory --format=csv,noheader,nounits 2>&1)
|
if ($LASTEXITCODE -ne 0) {
|
throw "nvidia-smi query failed: $($GpuRows -join ' ')"
|
}
|
foreach ($GpuRow in $GpuRows) {
|
if ($GpuRow -match '^\s*(\d+)\s*,\s*(.+?)\s*$') {
|
$GpuPid = [int]$Matches[1]
|
if ($TaskPids.Contains($GpuPid)) {
|
$NvidiaTaskProcesses.Add([ordered]@{
|
pid = $GpuPid
|
used_gpu_memory_raw = [string]$Matches[2]
|
raw_row = [string]$GpuRow
|
})
|
if ($GpuPid -eq $RootPid) {
|
$NvidiaRootPidSeen = $true
|
}
|
}
|
}
|
}
|
|
$GpuCounterPaths = New-Object 'System.Collections.Generic.HashSet[string]'
|
$GpuCounterInstances = New-Object System.Collections.Generic.List[object]
|
$GpuBytesByPid = @{}
|
foreach ($GpuInstanceName in @($GpuPerformanceCategory.GetInstanceNames())) {
|
$InstanceName = [string]$GpuInstanceName
|
if ($InstanceName -notmatch '(?i)(?:^|_)pid_(\d+)(?:_|$)') {
|
continue
|
}
|
$CounterPid = [int]$Matches[1]
|
if (-not $TaskPids.Contains($CounterPid)) {
|
continue
|
}
|
$CounterPath = "\GPU Process Memory($InstanceName)\Dedicated Usage"
|
if (-not $GpuCounterPaths.Add($CounterPath)) {
|
continue
|
}
|
$GpuCounter = New-Object System.Diagnostics.PerformanceCounter -ArgumentList @(
|
"GPU Process Memory",
|
"Dedicated Usage",
|
$InstanceName,
|
$true
|
)
|
try {
|
$DedicatedBytes = [double]$GpuCounter.NextValue()
|
} finally {
|
$GpuCounter.Dispose()
|
}
|
if ([double]::IsNaN($DedicatedBytes) -or [double]::IsInfinity($DedicatedBytes) -or $DedicatedBytes -lt 0) {
|
throw "Invalid GPU dedicated-usage value for $CounterPath`: $DedicatedBytes"
|
}
|
if (-not $GpuBytesByPid.ContainsKey($CounterPid)) {
|
$GpuBytesByPid[$CounterPid] = 0.0
|
}
|
$GpuBytesByPid[$CounterPid] += $DedicatedBytes
|
$GpuCounterInstances.Add([ordered]@{
|
pid = $CounterPid
|
instance_name = $InstanceName
|
path = $CounterPath
|
dedicated_usage_bytes = [int64]$DedicatedBytes
|
})
|
}
|
if ($GpuCounterInstances.Count -gt 0) {
|
$GpuCounterTaskInstanceSeen = $true
|
}
|
$TaskGpuMemoryBytes = if ($GpuBytesByPid.Count -eq 0) {
|
0.0
|
} else {
|
[double](($GpuBytesByPid.Values | Measure-Object -Sum).Sum)
|
}
|
if ($TaskGpuMemoryBytes -gt 0) {
|
$GpuDedicatedUsageNonzeroSeen = $true
|
}
|
$TaskGpuMemoryMiB = $TaskGpuMemoryBytes / 1MB
|
|
$StagingDirectories = @()
|
if (Test-Path -LiteralPath $OutputParent -PathType Container) {
|
$StagingDirectories = @(Get-ChildItem -LiteralPath $OutputParent -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name.StartsWith($StagingPrefix, [StringComparison]::Ordinal) })
|
}
|
$ChunkFiles = @($StagingDirectories | ForEach-Object {
|
Get-ChildItem -LiteralPath $_.FullName -Filter "*.flac" -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne "$MediaStem.audio.flac" }
|
})
|
$TempChunkBytes = if ($ChunkFiles.Count -eq 0) {
|
0L
|
} else {
|
[int64](($ChunkFiles | Measure-Object -Property Length -Sum).Sum)
|
}
|
|
$Samples.Add([pscustomobject][ordered]@{
|
timestamp_utc = [DateTime]::UtcNow.ToString("o")
|
elapsed_seconds = [Math]::Round($Stopwatch.Elapsed.TotalSeconds, 3)
|
root_python_pid = $RootPid
|
descendant_processes = [string]$DescendantEvidence
|
block_index = if ($null -eq $CurrentBlock) { "" } else { $CurrentBlock }
|
block_total = if ($null -eq $BlockTotal) { "" } else { $BlockTotal }
|
python_working_set_bytes = $PythonWorkingSet
|
ffmpeg_working_set_bytes = $FfmpegWorkingSet
|
combined_working_set_bytes = $CombinedWorkingSet
|
task_gpu_memory_mib = $TaskGpuMemoryMiB
|
nvidia_smi_task_processes = [string]($NvidiaTaskProcesses.ToArray() | ConvertTo-Json -Compress)
|
gpu_process_memory_instances = [string]($GpuCounterInstances.ToArray() | ConvertTo-Json -Compress)
|
temp_chunk_count = $ChunkFiles.Count
|
temp_chunk_bytes = $TempChunkBytes
|
staging_directory_count = $StagingDirectories.Count
|
})
|
} catch {
|
$SamplingErrors.Add(
|
"$([DateTime]::UtcNow.ToString('o')) $($_.Exception.Message) | $($_.ScriptStackTrace)"
|
)
|
}
|
|
$Process.Refresh()
|
if (-not $Process.HasExited) {
|
$LastScheduledMilliseconds += $SampleIntervalMilliseconds
|
if ($LastScheduledMilliseconds -le $Stopwatch.ElapsedMilliseconds) {
|
$LastScheduledMilliseconds = $Stopwatch.ElapsedMilliseconds + $SampleIntervalMilliseconds
|
}
|
$Delay = [int][Math]::Max(1, $LastScheduledMilliseconds - $Stopwatch.ElapsedMilliseconds)
|
Start-Sleep -Milliseconds $Delay
|
}
|
} while (-not $Process.HasExited)
|
|
$Process.WaitForExit()
|
Start-Sleep -Milliseconds 50
|
$CapturedLine = $null
|
while ($StdoutQueue.TryDequeue([ref]$CapturedLine)) {
|
[System.IO.File]::AppendAllText($StdoutPath, $CapturedLine + [Environment]::NewLine, (New-Object System.Text.UTF8Encoding($false)))
|
$CapturedLine = $null
|
}
|
while ($StderrQueue.TryDequeue([ref]$CapturedLine)) {
|
[System.IO.File]::AppendAllText($StderrPath, $CapturedLine + [Environment]::NewLine, (New-Object System.Text.UTF8Encoding($false)))
|
$CapturedLine = $null
|
}
|
Unregister-Event -SubscriptionId $StdoutSubscription.Id
|
Unregister-Event -SubscriptionId $StderrSubscription.Id
|
Remove-Job -Id $StdoutSubscription.Id -Force -ErrorAction SilentlyContinue
|
Remove-Job -Id $StderrSubscription.Id -Force -ErrorAction SilentlyContinue
|
$Stopwatch.Stop()
|
$ExitCode = [int]$Process.ExitCode
|
$EndTimeUtc = [DateTime]::UtcNow
|
|
$Samples | Export-Csv -LiteralPath $SamplesPath -NoTypeInformation -Encoding UTF8
|
$BlockPeaks = @($Samples | Where-Object { $_.block_index -ne "" } | Group-Object block_index | Sort-Object { [int]$_.Name } | ForEach-Object {
|
$Rows = @($_.Group)
|
[pscustomobject][ordered]@{
|
block_index = [int]$_.Name
|
block_total = [int]$Rows[-1].block_total
|
first_timestamp_utc = $Rows[0].timestamp_utc
|
last_timestamp_utc = $Rows[-1].timestamp_utc
|
sample_count = $Rows.Count
|
python_working_set_peak_bytes = [int64](Get-Maximum ($Rows | ForEach-Object { $_.python_working_set_bytes }))
|
ffmpeg_working_set_peak_bytes = [int64](Get-Maximum ($Rows | ForEach-Object { $_.ffmpeg_working_set_bytes }))
|
combined_working_set_peak_bytes = [int64](Get-Maximum ($Rows | ForEach-Object { $_.combined_working_set_bytes }))
|
task_gpu_memory_peak_mib = Get-Maximum ($Rows | ForEach-Object { $_.task_gpu_memory_mib })
|
temp_chunk_count_peak = [int](Get-Maximum ($Rows | ForEach-Object { $_.temp_chunk_count }))
|
temp_chunk_bytes_peak = [int64](Get-Maximum ($Rows | ForEach-Object { $_.temp_chunk_bytes }))
|
}
|
})
|
$BlockPeaks | Export-Csv -LiteralPath $BlockPeaksPath -NoTypeInformation -Encoding UTF8
|
|
$GlobalCombinedPeak = [int64](Get-Maximum ($Samples | ForEach-Object { $_.combined_working_set_bytes }))
|
$GlobalGpuPeak = Get-Maximum ($Samples | ForEach-Object { $_.task_gpu_memory_mib })
|
$GlobalTempCountPeak = [int](Get-Maximum ($Samples | ForEach-Object { $_.temp_chunk_count }))
|
$GlobalTempBytesPeak = [int64](Get-Maximum ($Samples | ForEach-Object { $_.temp_chunk_bytes }))
|
$RamGrowthBytes = $null
|
$RamGrowthPass = $true
|
if ($BlockPeaks.Count -ge 6) {
|
$FirstThreePeak = [int64](Get-Maximum ($BlockPeaks | Select-Object -First 3 | ForEach-Object { $_.combined_working_set_peak_bytes }))
|
$LastThreePeak = [int64](Get-Maximum ($BlockPeaks | Select-Object -Last 3 | ForEach-Object { $_.combined_working_set_peak_bytes }))
|
$RamGrowthBytes = $LastThreePeak - $FirstThreePeak
|
$RamGrowthPass = $RamGrowthBytes -le $RamGrowthLimitBytes
|
}
|
|
$IntervalValues = @()
|
for ($Index = 1; $Index -lt $Samples.Count; $Index++) {
|
$IntervalValues += ([double]$Samples[$Index].elapsed_seconds - [double]$Samples[$Index - 1].elapsed_seconds)
|
}
|
$ObservedBlockIndices = @($BlockPeaks | ForEach-Object { [int]$_.block_index })
|
$ExpectedBlockTotal = if ($BlockPeaks.Count -eq 0) { 0 } else { [int]$BlockPeaks[-1].block_total }
|
$AllBlocksSampled = $ExpectedBlockTotal -eq 0 -or (
|
$ObservedBlockIndices.Count -eq $ExpectedBlockTotal -and
|
(@(Compare-Object $ObservedBlockIndices @(1..$ExpectedBlockTotal))).Count -eq 0
|
)
|
$GpuEvidenceValid = $SkipValidation -or (
|
$NvidiaRootPidSeen -and
|
$GpuCounterTaskInstanceSeen -and
|
$GpuDedicatedUsageNonzeroSeen
|
)
|
$SamplingValid = $SamplingErrors.Count -eq 0 -and $Samples.Count -gt 0 -and $FfmpegSeen -and $AllBlocksSampled -and $GpuEvidenceValid
|
$ResourceGatesPass = (
|
$ExitCode -eq 0 -and
|
$SamplingValid -and
|
$GlobalCombinedPeak -le $RamLimitBytes -and
|
$GlobalGpuPeak -lt $GpuLimitMiB -and
|
$GlobalTempCountPeak -le 1 -and
|
$GlobalTempBytesPeak -lt $TempChunkLimitBytes -and
|
$RamGrowthPass
|
)
|
|
$GpuNameRows = @(& nvidia-smi --query-gpu=name --format=csv,noheader 2>&1)
|
$GpuName = if ($LASTEXITCODE -eq 0) { ($GpuNameRows -join "; ").Trim() } else { "QUERY_FAILED" }
|
$Summary = [ordered]@{
|
generated_at_utc = [DateTime]::UtcNow.ToString("o")
|
sampling_valid = $SamplingValid
|
sampling_errors = @($SamplingErrors)
|
configured_interval_milliseconds = $SampleIntervalMilliseconds
|
observed_interval_seconds = [ordered]@{
|
minimum = if ($IntervalValues.Count -eq 0) { $null } else { ($IntervalValues | Measure-Object -Minimum).Minimum }
|
average = if ($IntervalValues.Count -eq 0) { $null } else { ($IntervalValues | Measure-Object -Average).Average }
|
maximum = if ($IntervalValues.Count -eq 0) { $null } else { ($IntervalValues | Measure-Object -Maximum).Maximum }
|
}
|
root_python_pid = $RootPid
|
gpu_name = $GpuName
|
gpu_memory_source = "Windows GPU Process Memory Dedicated Usage for task PID set; nvidia-smi task rows retained"
|
nvidia_smi_root_pid_seen = $NvidiaRootPidSeen
|
gpu_counter_task_instance_seen = $GpuCounterTaskInstanceSeen
|
gpu_dedicated_usage_nonzero_seen = $GpuDedicatedUsageNonzeroSeen
|
gpu_evidence_valid = $GpuEvidenceValid
|
ffmpeg_descendant_seen = $FfmpegSeen
|
sample_count = $Samples.Count
|
observed_block_indices = $ObservedBlockIndices
|
expected_block_total = $ExpectedBlockTotal
|
all_blocks_sampled = $AllBlocksSampled
|
combined_working_set_peak_bytes = $GlobalCombinedPeak
|
combined_working_set_limit_bytes = [int64]$RamLimitBytes
|
task_gpu_memory_peak_mib = $GlobalGpuPeak
|
task_gpu_memory_limit_mib_exclusive = $GpuLimitMiB
|
temp_chunk_count_peak = $GlobalTempCountPeak
|
temp_chunk_count_limit = 1
|
temp_chunk_bytes_peak = $GlobalTempBytesPeak
|
temp_chunk_bytes_limit_exclusive = [int64]$TempChunkLimitBytes
|
ram_growth_formula = "max(last_3_block_ram_peaks)-max(first_3_block_ram_peaks)"
|
ram_growth_bytes = $RamGrowthBytes
|
ram_growth_limit_bytes = [int64]$RamGrowthLimitBytes
|
ram_growth_pass = $RamGrowthPass
|
target_exit_code = $ExitCode
|
target_elapsed_seconds = [Math]::Round($Stopwatch.Elapsed.TotalSeconds, 3)
|
resource_gates_pass = $ResourceGatesPass
|
}
|
Write-JsonFile $SummaryPath $Summary
|
|
$ValidationExitCode = $null
|
if (-not $SkipValidation) {
|
$ValidationArguments = New-Object System.Collections.Generic.List[string]
|
$ValidationArguments.Add("-X")
|
$ValidationArguments.Add("utf8")
|
$ValidationArguments.Add("-B")
|
$ValidationArguments.Add($ValidatorPath)
|
$ValidationArguments.Add("validate")
|
$ValidationArguments.Add("--original-source")
|
$ValidationArguments.Add($OriginalSourcePath)
|
$ValidationArguments.Add("--source-before")
|
$ValidationArguments.Add($SourceBeforePath)
|
$ValidationArguments.Add("--media")
|
$ValidationArguments.Add($VideoPath)
|
$ValidationArguments.Add("--output-dir")
|
$ValidationArguments.Add($OutputPath)
|
$ValidationArguments.Add("--evidence")
|
$ValidationArguments.Add($AcceptancePath)
|
$ValidationArguments.Add("--resource-summary")
|
$ValidationArguments.Add($SummaryPath)
|
if ($ExpectedSegments -gt 0) {
|
$ValidationArguments.Add("--expected-segments")
|
$ValidationArguments.Add([string]$ExpectedSegments)
|
}
|
& $PythonPath @ValidationArguments
|
$ValidationExitCode = [int]$LASTEXITCODE
|
}
|
|
$CommandEvidence = [ordered]@{
|
executable = $PythonPath
|
arguments = @($Arguments)
|
command_line = "$PythonPath $ArgumentString"
|
working_directory = (Get-Location).Path
|
start_time_utc = $StartTimeUtc.ToString("o")
|
end_time_utc = $EndTimeUtc.ToString("o")
|
elapsed_seconds = [Math]::Round($Stopwatch.Elapsed.TotalSeconds, 3)
|
root_python_pid = $RootPid
|
exit_code = $ExitCode
|
validation_exit_code = $ValidationExitCode
|
sampling_interval_milliseconds = $SampleIntervalMilliseconds
|
}
|
Write-JsonFile $CommandPath $CommandEvidence
|
|
Write-Output "target_exit_code=$ExitCode"
|
Write-Output "elapsed_seconds=$([Math]::Round($Stopwatch.Elapsed.TotalSeconds, 3))"
|
Write-Output "resource_gates_pass=$ResourceGatesPass"
|
Write-Output "validation_exit_code=$ValidationExitCode"
|
exit $ExitCode
|