#!/usr/bin/env python3 """Summarize opt-in client marker reports using explicit, half-open row ranges.""" import argparse import json import math from pathlib import Path import statistics def describe(values): ordered = sorted(values) if not ordered: raise ValueError('No valid marker samples in the selected range') return { 'count': len(ordered), 'mean': statistics.mean(ordered), 'p50': statistics.median(ordered), 'p95': ordered[int((len(ordered)-1)*.95)], 'p99': ordered[int((len(ordered)-1)*.99)], 'minimum': ordered[0], 'maximum': ordered[-1], } def summarize(rows, selection): start, end = selection['start_row'], selection['end_row'] if not 0 <= start < end <= len(rows): raise ValueError('Range must select complete rows from the report') samples = rows[start:end] seen = set() for sample in samples: if not all(isinstance(v, (int, float)) and math.isfinite(v) for v in sample.values()): raise ValueError('Sample contains a nonfinite value') if not 0 <= sample['lowerMs'] <= sample['upperMs'] <= 5000: raise ValueError('Invalid refresh bracket') key = (sample['counter'], sample['sourceDrawMs']) if key in seen: raise ValueError('A source marker was counted more than once') seen.add(key) return { **selection, 'samples': len(samples), 'sample_span_seconds': (samples[-1]['drawMs']-samples[0]['drawMs'])/1000, 'marker_event_to_paint_ms': describe([(s['lowerMs']+s['upperMs'])/2 for s in samples]), 'lower_bound_ms': describe([s['lowerMs'] for s in samples]), 'upper_bound_ms': describe([s['upperMs'] for s in samples]), 'refresh_bracket_width_ms': describe([s['upperMs']-s['lowerMs'] for s in samples]), 'decoded_to_draw_ms': describe([s['decodeToDrawMs'] for s in samples]), 'marker_analysis_ms': describe([s['analysisMs'] for s in samples]), } def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--report', type=Path, required=True) parser.add_argument('--range', type=Path, action='append', required=True, dest='ranges') parser.add_argument('--out', type=Path, required=True) args = parser.parse_args() rows = [json.loads(line) for line in args.report.read_text().splitlines() if line.strip()] trials = [summarize(rows, json.loads(path.read_text())) for path in args.ranges] result = { 'method': '50 ms source marker events; first decodable echo on production VideoSurface; midpoint of adjacent-rAF brackets', 'scope': 'Source-screen marker to camera exposure/capture, Wi-Fi/UDP, native reassembly, local WebSocket, JPEG decode and canvas draw', 'limitations': [ 'rAF brackets estimate display scheduling; panel scanout and pixel response are not measured', 'Mixed-exposure or unrecognized markers are excluded; first decodable echo can follow an earlier ambiguous image', 'Not every displayed frame is a sample; accepted samples describe this calibration scene and network', 'decodeToDrawMs starts after JPEG decoding, not at UDP or WebSocket arrival', ], 'physical_panel_response_measured': False, 'trials': trials, } args.out.parent.mkdir(parents=True, exist_ok=True) args.out.write_text(json.dumps(result, ensure_ascii=False, indent=2)+'\n', encoding='utf-8') for trial in trials: print(json.dumps({k: trial[k] for k in ('fb_count', 'samples', 'marker_event_to_paint_ms')})) if __name__ == '__main__': main()