Files
StellarX-kaifa/scripts/analyze-input-probe.ps1
T

284 lines
13 KiB
PowerShell

param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$ErrorActionPreference = 'Stop'
$resolved = Resolve-Path -LiteralPath $Path
$item = Get-Item -LiteralPath $resolved
$file = if ($item.PSIsContainer) {
Get-ChildItem -LiteralPath $item.FullName -Filter '*.jsonl' -File |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
}
else {
$item
}
if (-not $file) { throw "No JSONL file found: $Path" }
$records = New-Object System.Collections.Generic.List[object]
$lineNumber = 0
Get-Content -LiteralPath $file.FullName | ForEach-Object {
$lineNumber++
try { $records.Add(($_ | ConvertFrom-Json)) }
catch { throw "Invalid JSONL at line $lineNumber in $($file.FullName): $($_.Exception.Message)" }
}
$sequenceStrict = $true
for ($index = 1; $index -lt $records.Count; $index++) {
if ([uint64]$records[$index].sequence -le [uint64]$records[$index - 1].sequence) {
$sequenceStrict = $false
break
}
}
$immQueries = @($records | Where-Object recordType -eq 'imm_query')
$successfulResults = @($immQueries | Where-Object immQueryStatus -eq 'success')
$echoSummaries = @($records | Where-Object recordType -eq 'echo_correlation')
$textLeaks = @($records | Where-Object {
$_.messageCategory -eq 'text' -and $_.wParamHex -ne $null
})
$lost = @($records | Where-Object recordType -eq 'records_lost')
$lostTotal = 0
foreach ($lostRecord in $lost) {
if ($null -ne $lostRecord.lostCount) {
$lostTotal += [int]$lostRecord.lostCount
}
else {
$lostTotal++
}
}
$attach = $records | Where-Object recordType -eq 'window_attach' | Select-Object -First 1
$closeObserved = @($records | Where-Object messageName -eq 'WM_CLOSE').Count -gt 0
$sessionEndObserved = @($records | Where-Object recordType -eq 'session_end').Count -gt 0
$experimentConfigs = @($records | Where-Object recordType -eq 'experiment_config')
if ($experimentConfigs.Count -gt 1) {
throw "More than one experiment_config record was found: $($experimentConfigs.Count)"
}
$experimentConfig = $experimentConfigs | Select-Object -First 1
$experimentTargetEnters = @()
$experimentTargetLeaves = @()
$experimentEnters = @()
$experimentLeaves = @()
$matchingEasyX = @()
$easyxVisibility = $null
$experimentValidity = 'not_configured'
$imePositionAttempts = @()
$imePositionSuccesses = @()
$imePositionResultRecords = @()
$imePositionOrphans = @()
$runtimeFailOpenEnters = @()
$runtimeFailOpenLeaves = @()
$runtimeFailOpenReason = $null
if ($experimentConfig) {
$schemaVersions = @($records | Select-Object -ExpandProperty schemaVersion -Unique)
$experimentTargetEnters = @($records | Where-Object {
$_.recordType -eq 'wndproc_enter' -and
$_.experimentId -eq $experimentConfig.experimentId -and
$_.ruleId -eq $experimentConfig.ruleId
})
$targetCallIds = @($experimentTargetEnters | Select-Object -ExpandProperty wndProcCallId)
$experimentTargetLeaves = @($records | Where-Object {
$_.recordType -eq 'wndproc_leave' -and
$targetCallIds -contains $_.wndProcCallId
})
$runtimeFailOpenEnters = @($experimentTargetEnters | Where-Object failOpenApplied -eq $true)
$runtimeFailOpenCallIds = @($runtimeFailOpenEnters | Select-Object -ExpandProperty wndProcCallId)
$runtimeFailOpenLeaves = @($experimentTargetLeaves | Where-Object {
$runtimeFailOpenCallIds -contains $_.wndProcCallId -and $_.failOpenApplied -eq $true
})
$runtimeFailOpenRecords = @($runtimeFailOpenEnters) + @($runtimeFailOpenLeaves)
$runtimeFailOpenReasons = @($runtimeFailOpenRecords |
Select-Object -ExpandProperty failOpenReason -Unique)
if ($runtimeFailOpenReasons.Count -eq 1) {
$runtimeFailOpenReason = [string]$runtimeFailOpenReasons[0]
}
$experimentEnters = @($experimentTargetEnters | Where-Object ruleMatched -eq $true)
$matchedCallIds = @($experimentEnters | Select-Object -ExpandProperty wndProcCallId)
$experimentLeaves = @($records | Where-Object {
$_.recordType -eq 'wndproc_leave' -and
$matchedCallIds -contains $_.wndProcCallId
})
$easyXMessageType = if ([string]$experimentConfig.targetMessage -match 'UP$') {
'WM_KEYUP'
}
else {
'WM_KEYDOWN'
}
$targetScanSpecified = $null -ne $experimentConfig.targetScanCode
$matchingEasyX = @($records | Where-Object {
$_.recordType -eq 'easyx_key' -and
$_.easyxMessageType -eq $easyXMessageType -and
[int]$_.easyxVkCode -eq [int]$experimentConfig.targetVirtualKey -and
(-not $targetScanSpecified -or
[int]$_.easyxScanCode -eq [int]$experimentConfig.targetScanCode)
})
$imePositionResultRecords = @($records | Where-Object recordType -eq 'ime_position')
# 兼容本修复前把定位结果直接挂在 wndproc_enter 上的 schema v2 日志。
$legacyImePositionAttempts = @($records | Where-Object {
$_.recordType -eq 'wndproc_enter' -and $_.imePositionAttempted -eq $true
})
$imePositionAttempts = @($imePositionResultRecords) + @($legacyImePositionAttempts)
$imePositionSuccesses = @($imePositionAttempts | Where-Object {
$_.imePositionContextAvailable -eq $true -and
$_.compositionPositionSucceeded -eq $true -and
$_.candidatePositionSucceeded -eq $true
})
$sequenceLookup = @{}
foreach ($entry in $records) { $sequenceLookup[[string]$entry.sequence] = $true }
$imePositionOrphans = @($imePositionResultRecords | Where-Object {
$null -eq $_.parentSequence -or
-not $sequenceLookup.ContainsKey([string]$_.parentSequence) -or
[uint64]$_.parentSequence -ge [uint64]$_.sequence
})
$easyxVisibility = if ($matchingEasyX.Count -eq 0) {
'absent'
}
elseif ($matchingEasyX.Count -eq 1) {
'present_once'
}
else {
'present_multiple'
}
if ($schemaVersions.Count -ne 1 -or [int]$schemaVersions[0] -ne 2) {
$experimentValidity = 'invalid_schema'
}
elseif (-not $sequenceStrict) {
$experimentValidity = 'inconclusive_sequence'
}
elseif ($textLeaks.Count -gt 0) {
$experimentValidity = 'inconclusive_text_leak'
}
elseif ($lostTotal -gt 0) {
$experimentValidity = 'inconclusive_capture_loss'
}
elseif ($imePositionOrphans.Count -gt 0) {
$experimentValidity = 'inconclusive_ime_position_link'
}
elseif (-not $closeObserved) {
$experimentValidity = 'inconclusive_close_missing'
}
elseif (-not $sessionEndObserved) {
$experimentValidity = 'inconclusive_session_end_missing'
}
elseif (-not $experimentConfig.configurationValid) {
$experimentValidity = 'invalid_configuration'
}
elseif (-not $experimentConfig.ruleEligible) {
$experimentValidity = 'ineligible_fail_open'
}
elseif ([string]$experimentConfig.inputSource -eq 'Manual' -and
(-not $experimentConfig.imePositionConfigured -or $imePositionSuccesses.Count -eq 0)) {
$experimentValidity = 'inconclusive_ime_position'
}
elseif ($experimentTargetEnters.Count -ne [int]$experimentConfig.maxMatches) {
$experimentValidity = 'unexpected_target_count'
}
elseif ([string]$experimentConfig.requestedAction -eq 'ObserveAndConsume' -and
$experimentEnters.Count -eq 0 -and
$runtimeFailOpenEnters.Count -eq [int]$experimentConfig.maxMatches -and
$runtimeFailOpenLeaves.Count -eq $runtimeFailOpenEnters.Count -and
$runtimeFailOpenReasons.Count -eq 1 -and
-not [string]::IsNullOrWhiteSpace($runtimeFailOpenReason) -and
@($runtimeFailOpenLeaves | Where-Object {
$_.actualAction -ne 'ObserveAndForward' -or $_.forwarded -ne $true
}).Count -eq 0) {
$experimentValidity = 'runtime_fail_open'
}
elseif ($experimentEnters.Count -ne [int]$experimentConfig.maxMatches) {
$experimentValidity = 'unexpected_match_count'
}
elseif ($experimentLeaves.Count -ne $experimentEnters.Count) {
$experimentValidity = 'missing_leave_record'
}
elseif ([string]$experimentConfig.requestedAction -eq 'ObserveAndConsume' -and
@($experimentLeaves | Where-Object actualAction -ne 'ObserveAndConsume').Count -gt 0) {
$experimentValidity = 'consume_action_mismatch'
}
elseif ([string]$experimentConfig.requestedAction -eq 'ObserveAndConsume' -and
@($experimentLeaves | Where-Object forwarded -eq $true).Count -gt 0) {
$experimentValidity = 'consume_was_forwarded'
}
elseif ([string]$experimentConfig.requestedAction -eq 'ObserveAndConsume' -and
@($experimentLeaves | Where-Object forwardResultHex -ne '0x0000000000000000').Count -gt 0) {
$experimentValidity = 'consume_result_nonzero'
}
elseif ([string]$experimentConfig.requestedAction -eq 'ObserveAndForward' -and
@($experimentLeaves | Where-Object actualAction -ne 'ObserveAndForward').Count -gt 0) {
$experimentValidity = 'forward_action_mismatch'
}
elseif ([string]$experimentConfig.requestedAction -eq 'ObserveAndForward' -and
@($experimentLeaves | Where-Object forwarded -ne $true).Count -gt 0) {
$experimentValidity = 'forward_was_not_forwarded'
}
else {
$experimentValidity = 'valid'
}
}
$correlationCounts = [ordered]@{}
foreach ($entry in $echoSummaries) {
$key = "$($entry.echoChannel):$($entry.correlationStatus)"
if (-not $correlationCounts.Contains($key)) { $correlationCounts[$key] = 0 }
$correlationCounts[$key]++
}
[pscustomobject]@{
file = $file.FullName
recordCount = $records.Count
schemaVersions = @($records | Select-Object -ExpandProperty schemaVersion -Unique)
sequenceStrict = $sequenceStrict
recordsLost = $lostTotal
lossRecordCount = $lost.Count
textRawWParamLeaks = $textLeaks.Count
isWindowUnicode = $attach.isWindowUnicode
ansiCodePage = $attach.ansiCodePage
wndProcThreadIds = @($records | Where-Object sourceBackend -eq 'Win32WndProc' |
Select-Object -ExpandProperty threadId -Unique)
hostThreadIds = @($records | Where-Object sourceBackend -eq 'HostEventLoop' |
Select-Object -ExpandProperty threadId -Unique)
imeStartCount = @($records | Where-Object messageName -eq 'WM_IME_STARTCOMPOSITION').Count
imeCompositionCount = @($records | Where-Object messageName -eq 'WM_IME_COMPOSITION').Count
imeEndCount = @($records | Where-Object messageName -eq 'WM_IME_ENDCOMPOSITION').Count
inputLanguageChangeRequestCount = @($records |
Where-Object messageName -eq 'WM_INPUTLANGCHANGEREQUEST').Count
inputLanguageChangeCount = @($records | Where-Object messageName -eq 'WM_INPUTLANGCHANGE').Count
immQueryCount = $immQueries.Count
successfulResultCount = $successfulResults.Count
successfulUtf16Lengths = @($successfulResults | Select-Object -ExpandProperty utf16Length)
successfulScalarCounts = @($successfulResults | Select-Object -ExpandProperty scalarCount)
digestPresentCount = @($successfulResults | Where-Object sessionDigest -ne $null).Count
correlationCounts = [pscustomobject]$correlationCounts
closeObserved = $closeObserved
sessionEndObserved = $sessionEndObserved
experimentConfigured = $null -ne $experimentConfig
experimentId = if ($experimentConfig) { $experimentConfig.experimentId } else { $null }
ruleId = if ($experimentConfig) { $experimentConfig.ruleId } else { $null }
ruleEligible = if ($experimentConfig) { [bool]$experimentConfig.ruleEligible } else { $null }
experimentFailOpenReason = if ($experimentConfig) { $experimentConfig.failOpenReason } else { $null }
runtimeFailOpenReason = $runtimeFailOpenReason
requestedAction = if ($experimentConfig) { $experimentConfig.requestedAction } else { $null }
targetMessage = if ($experimentConfig) { $experimentConfig.targetMessage } else { $null }
targetVirtualKey = if ($experimentConfig) { $experimentConfig.targetVirtualKey } else { $null }
targetScanCode = if ($experimentConfig) { $experimentConfig.targetScanCode } else { $null }
baselineId = if ($experimentConfig) { $experimentConfig.baselineId } else { $null }
targetObservationCount = $experimentTargetEnters.Count
targetLeaveCount = $experimentTargetLeaves.Count
failOpenObservationCount = @($experimentTargetEnters | Where-Object failOpenApplied -eq $true).Count
failOpenForwardedCount = @($experimentTargetLeaves | Where-Object {
$_.failOpenApplied -eq $true -and $_.forwarded -eq $true
}).Count
ruleMatchCount = $experimentEnters.Count
consumedMatchCount = @($experimentEnters | Where-Object actualAction -eq 'ObserveAndConsume').Count
forwardedMatchCount = @($experimentLeaves | Where-Object forwarded -eq $true).Count
matchingEasyXKeyCount = $matchingEasyX.Count
easyxVisibility = $easyxVisibility
imePositionAttemptCount = $imePositionAttempts.Count
imePositionSuccessCount = $imePositionSuccesses.Count
imePositionReady = $imePositionSuccesses.Count -gt 0
imePositionResultRecordCount = $imePositionResultRecords.Count
imePositionOrphanCount = $imePositionOrphans.Count
experimentValidity = $experimentValidity
}