jenkins 9204773e7d av-test: publish bounded continuity watch and recent observations
Vendor tested Lesavka observer assets and numeric receipt schema from 6b97ceb1. Verified 65 JS tests, 10 service tests, kustomize render and Kubernetes client dry-run. No media upload, capture activation or changes to the homepage and private OCR.
2026-10-22 21:47:13 -05:00

112 lines
6.2 KiB
JavaScript

// Only the explicit numeric projection below can leave the browser. Never
// pass a receipt, device object, error string, frame, PCM or DOM value to fetch.
export function reportMetrics(receipt) {
const finite=value=>typeof value==='number'&&Number.isFinite(value)&&value>=0 ? value : null;
const v=receipt.video??{},a=receipt.audio??{},s=receipt.settings??{},p=receipt.paired??{};
const c=p.continuity??{},pilot=p.pilot??{},recent=pilot.recent??{};
const estimate=typeof p.estimatedMedianSignedMs==='number'&&Number.isFinite(p.estimatedMedianSignedMs)?p.estimatedMedianSignedMs:null;
return Object.fromEntries(Object.entries({
elapsed_ms:receipt.elapsedMs,
video_width:s.video?.width,video_height:s.video?.height,video_fps:s.video?.frameRate,
audio_rate:s.audio?.sampleRate,audio_channels:s.audio?.channelCount,
video_callbacks:v.callbacks,video_unobserved:v.unobservedPresentedFrames,
video_resets:v.clockOrCounterResets,video_interval_p95_ms:v.callbackIntervalP95Ms,
video_gap_max_ms:v.callbackGapMaxMs,audio_input_frames:a.inputFrames,
audio_processed_frames:a.processedFrames,audio_zero_frames:a.exactZeroFrames,
audio_rms:a.rms,audio_peak:a.peak,audio_context_rate:a.sampleRate,
paired_audio_signals:p.audioSignals,paired_video_signals:p.videoSignals,
paired_matches:p.pairs,paired_ambiguous:p.ambiguous,paired_overflow:p.overflow,
paired_estimate_audio_late_ms:estimate===null?null:Math.max(0,estimate),
paired_estimate_audio_early_ms:estimate===null?null:Math.max(0,-estimate),
marker_frames:c.frames,marker_bad:c.badMarkers,marker_ambiguous:c.ambiguousGaps,
marker_missing_fraction:c.missingFraction,marker_duplicate_fraction:c.duplicateFraction,
pilot_windows:pilot.windows,pilot_gap_max_ms:pilot.gapMaxMs,
pilot_missing_fraction:pilot.missingFraction,pilot_trailing_gap_ms:pilot.trailingGapMs,
pilot_discontinuities:pilot.discontinuities,
pilot_recent_windows:recent.observedWindows,pilot_recent_observed_ms:recent.observedMs,
pilot_recent_age_ms:recent.observationAgeMs,pilot_recent_absent_fraction:recent.absentFraction,
pilot_recent_absence_max_ms:recent.absenceMaxMs,pilot_recent_trailing_ms:recent.trailingAbsenceMs,
pilot_recent_interruptions:recent.interruptions,
pilot_recent_coverage_complete:typeof recent.coverageComplete==='boolean'?Number(recent.coverageComplete):null,
}).map(([key,value])=>[key,finite(value)]));
}
export function stopState(reason) {
if(reason==='120 seconds complete')return 'completed';
if(reason==='page hidden; timing invalid')return 'hidden';
if(reason==='page closed')return 'closed';
if(reason==='device ended'||reason==='device selection changed')return 'device-change';
if(reason==='operator'||reason==='selecting devices')return 'stopped';
return 'failed';
}
export class RunReporter {
constructor(status,request=globalThis.fetch.bind(globalThis)) {
this.status=status;this.request=request;this.pending=null;this.busy=false;
this.terminal=false;this.sequence=0;this.lastProgress=-Infinity;this.available=true;
status('Reporting: connecting…');
this.ready=this.open();
}
async open() {
try {
const r=await this.request('/av-test/api/start',{method:'POST',credentials:'omit',
headers:{'Content-Type':'application/json'},body:'{}',signal:AbortSignal.timeout(5000)});
if(!r.ok)throw new Error('start rejected');
const data=await r.json();
if(!/^[a-f0-9]{32}$/.test(data.run_id)||!/^[A-Za-z0-9_-]{43}$/.test(data.write_token))throw new Error('bad receipt');
this.runId=data.run_id;this.token=data.write_token;
this.status(`Reporting enabled · test ${this.runId.slice(0,8)}`);
} catch {
this.available=false;this.status('Reporting unavailable · Save metrics remains available');
}
}
observe(receipt) {
if(this.terminal)return;
// Paired tests reset elapsed time when the known source first arrives.
// A long signal wait must not suppress the next minute of progress.
if(receipt.elapsedMs<this.lastProgress)this.lastProgress=-Infinity;
if(receipt.elapsedMs-this.lastProgress<10000)return;
this.lastProgress=receipt.elapsedMs;this.queue('running',receipt);
}
finish(receipt) {
if(this.terminal)return;
this.terminal=true;this.queue(stopState(receipt.reason),receipt);
}
queue(state,receipt) {
// One latest snapshot, not a growing queue. Terminal state supersedes
// pending progress; a stopped run never becomes running again.
this.pending={state,metrics:reportMetrics(receipt)};
void this.flush();
}
async flush() {
if(this.busy)return;
this.busy=true;
try {
await this.ready;
if(!this.available){this.pending=null;return;}
while(this.pending) {
const report=this.pending;this.pending=null;
const payload={run_id:this.runId,sequence:++this.sequence,...report};
let delivered=false;
for(let attempt=0;attempt<2&&!delivered;attempt++) {
try {
const response=await this.request('/av-test/api/report',{
method:'POST',credentials:'omit',keepalive:true,
headers:{'Content-Type':'application/json',Authorization:`Bearer ${this.token}`},
body:JSON.stringify(payload),signal:AbortSignal.timeout(5000),
});
delivered=response.ok;
if(response.status>=400&&response.status<500)break;
} catch { /* No raw exception or endpoint response is displayed. */ }
}
if(report.state!=='running') {
this.status(delivered ? `Result reported · test ${this.runId.slice(0,8)}` :
'Result not reported · use Save metrics');
} else if(!delivered) {
this.status('Reporting interrupted · local test continues');
}
}
} finally {this.busy=false;}
}
}