284 lines
13 KiB
PowerShell
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
|
|
}
|