| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960 |
- /**
- * 票 33 真机回归判据计算(消费 t33/probe-jitter.mjs 产出的 jitter-report.json)。
- *
- * 判据(票 33 验收第 2 条,锚定票 26 探针方法):
- * (a) 70s 静置内,任何 ≥100ms 长任务不得落在 map-snapshot 响应后 ±500ms;
- * (b) 30s/60s 窗口帧间隔 p95 ≤ 33ms。
- *
- * 另外输出全程分桶,用于把「刷新窗口劣化」与「环境全程劣化」区分开——
- * 若环境本身全程 >100ms/帧(如软件渲染 + 城市白模持续流送),判据 (b) 的绝对值
- * 在该环境里不可达,须如实记录并给出同环境无修复基线对照。
- */
- import { readFileSync } from 'node:fs'
- const file = process.argv[2] || new URL('./jitter-report.json', import.meta.url).pathname.replace(/^\//, '')
- const report = JSON.parse(readFileSync(file, 'utf8'))
- // ---- (a) map-snapshot 响应时刻(XHR loadend 时刻即响应完成)----
- // 判据口径:**静置刷新期**的 snapshot(首载的 snapshot 与瓦片/几何首载重叠,另列不算违规)。
- const snapTimesAll = report.requests.filter((r) => r.url.includes('map-snapshot')).map((r) => r.t)
- const firstSnap = snapTimesAll[0] || 0
- const bootUntil = firstSnap + 15000 // 首载窗口:首个 snapshot 后 15s 内的 snapshot 都按初载处理
- const snapTimes = snapTimesAll.filter((t) => t > bootUntil)
- const longTasks = (report.longTasksAll || report.longTaskCorrelation || []).filter((t) => t.dur >= 100)
- const WINDOW = 500
- const violations = []
- for (const task of longTasks) {
- for (const snap of snapTimes) {
- if (Math.abs(task.start - snap) <= WINDOW || Math.abs(task.start + task.dur - snap) <= WINDOW) {
- violations.push({ taskStart: Math.round(task.start), dur: task.dur, snapAt: Math.round(snap) })
- break
- }
- }
- }
- console.log(`(a) map-snapshot 响应:初载=${snapTimesAll.length - snapTimes.length} 静置刷新=${snapTimes.length};≥100ms 长任务=${longTasks.length}`)
- console.log(`(a) 落在 snapshot ±${WINDOW}ms 的长任务=${violations.length}${violations.length ? ' ←判据不满足' : ' ←判据通过'}`)
- for (const v of violations.slice(0, 10)) console.log(` task@${v.taskStart}ms dur=${v.dur}ms ↔ snap@${v.snapAt}ms`)
- // ---- (b) 帧间隔 p95:30s/60s 刷新 tick 窗口(tick 后 0–10s,即 30–40s / 60–70s)vs 全程 ----
- const p95 = (values) => {
- if (!values.length) return null
- const sorted = [...values].sort((a, b) => a - b)
- return sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))]
- }
- const buckets = report.frameBuckets || []
- const refreshDts = []
- const idleDts = []
- for (const bucket of buckets) {
- const sec = bucket.from / 1000
- const isRefreshWindow = (sec >= 30 && sec < 40) || (sec >= 60 && sec < 70)
- ;(isRefreshWindow ? refreshDts : idleDts).push(...bucket.dts)
- }
- const fmt = (v) => (v === null ? 'n/a' : Number(v).toFixed(1))
- console.log('\n(b) 帧间隔 p95:')
- console.log(` 30s/60s 刷新窗口(30–40s,60–70s): p95=${fmt(p95(refreshDts))}ms n=${refreshDts.length}${p95(refreshDts) <= 33 ? ' ←≤33ms 判据通过' : ' ←判据不满足'}`)
- console.log(` 其余静置窗口(对照): p95=${fmt(p95(idleDts))}ms n=${idleDts.length}`)
- console.log('\n(b) 5s 桶明细:')
- for (const w of report.fpsWindows) {
- const tag = (w.from >= 30000 && w.from < 40000) || (w.from >= 60000 && w.from < 70000) ? ' <— 刷新窗口' : ''
- console.log(` ${String(Math.round(w.from / 1000)).padStart(3)}s p50=${fmt(w.p50)} p95=${fmt(w.p95)}${tag}`)
- }
|