| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- /**
- * 票 26 消融实验:拦截 `/map-snapshot`(让 freshness tick 的 refresh 拿不到新页),
- * 其余端点与 GIS 资源照常。若 30s/60s 周期性帧 stall 消失 → 「refresh→重建」因果链成立。
- *
- * 运行:node .scratch/cockpit-real-data-wayfinding/verify/artifacts/t26/probe-jitter-ablate.mjs
- */
- import { cdpLaunch } from '../../../../cockpit-shell-panel-layout/verify/cdp-driver.mjs'
- const CHROME = process.env.CHROME_PATH || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
- const TARGET_URL = process.env.T26_URL || 'http://127.0.0.1:8000/#/cockpit'
- const WAIT_MS = Number(process.env.T26_WAIT_MS || 70000)
- const POLL_MS = 250
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
- function stats(values) {
- if (!values.length) return null
- const sorted = [...values].sort((a, b) => a - b)
- const at = (q) => sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))]
- return { n: sorted.length, p50: at(0.5), p95: at(0.95), max: sorted[sorted.length - 1] }
- }
- const browser = await cdpLaunch({ executablePath: CHROME, width: 1280, height: 800 })
- console.log(`Chrome: ${await browser.version()} [消融:map-snapshot 全程 500]`)
- const page = await browser.newPage()
- await page.goto(TARGET_URL, { timeout: 60000 })
- await page.waitForSelector('.cockpit-shell', { timeout: 20000 })
- // 采样器 + 消融补丁:map-snapshot 请求直接失败(fetch 与 XHR 两条路径都拦)。
- await page.evaluate(() => {
- window.__t26 = { frames: [], longTasks: [], blocked: 0 }
- let last = performance.now()
- const tick = (now) => {
- window.__t26.frames.push({ t: now, dt: now - last })
- last = now
- requestAnimationFrame(tick)
- }
- requestAnimationFrame(tick)
- try {
- new PerformanceObserver((list) => {
- for (const entry of list.getEntries()) window.__t26.longTasks.push({ start: entry.startTime, dur: entry.duration })
- }).observe({ entryTypes: ['longtask'] })
- } catch { /* 忽略 */ }
- const blocked = (url) => String(url).includes('map-snapshot')
- // 只拦 15s 之后的 map-snapshot:首屏加载与 auto-batch(0–8s)照常,
- // 消融目标是 30s 起的 freshness refresh 轮 —— 这样图上对象仍在,只有"重载"被摘除。
- const armAt = performance.now() + 15000
- const shouldBlock = () => performance.now() > armAt
- const nativeFetch = window.fetch.bind(window)
- window.fetch = async (...args) => {
- const url = String((args[0] && args[0].url) || args[0])
- if (blocked(url) && shouldBlock()) {
- window.__t26.blocked += 1
- return { ok: false, status: 500, json: async () => ({ code: 500, msg: 'ablated' }), headers: { get: () => null } }
- }
- return nativeFetch(...args)
- }
- const XHR = XMLHttpRequest.prototype
- const nativeOpen = XHR.open
- XHR.open = function (method, url, ...rest) {
- this.__t26blocked = blocked(url)
- return nativeOpen.call(this, method, url, ...rest)
- }
- const nativeSend = XHR.send
- XHR.send = function (...args) {
- if (this.__t26blocked && shouldBlock()) {
- window.__t26.blocked += 1
- Object.defineProperty(this, 'readyState', { configurable: true, get: () => 4 })
- Object.defineProperty(this, 'status', { configurable: true, get: () => 500 })
- Object.defineProperty(this, 'responseText', { configurable: true, get: () => '{"code":500,"msg":"ablated"}' })
- Object.defineProperty(this, 'response', { configurable: true, get: () => '{"code":500,"msg":"ablated"}' })
- setTimeout(() => {
- this.dispatchEvent(new Event('readystatechange'))
- this.dispatchEvent(new Event('load'))
- this.dispatchEvent(new Event('loadend'))
- }, 20)
- return undefined
- }
- return nativeSend.apply(this, args)
- }
- })
- if (await page.$('[data-cockpit-intro]')) {
- await page.click('[data-cockpit-intro]')
- await page.waitForSelector('[data-cockpit-intro]', { hidden: true, timeout: 10000 }).catch(() => {})
- }
- await page.waitForFunction(
- () => ['ready', 'failed', 'disabled'].includes(document.querySelector('.cockpit-shell__map .map-canvas')?.dataset.mapEngine),
- { timeout: 40000 },
- ).catch(() => {})
- await sleep(3000)
- console.log(`静置采样 ${(WAIT_MS / 1000).toFixed(0)}s…`)
- const deadline = Date.now() + WAIT_MS
- while (Date.now() < deadline) await sleep(POLL_MS)
- const result = await page.evaluate(() => {
- const frames = window.__t26.frames
- const start = frames.length ? frames[0].t : 0
- const out = []
- let bucket = []
- let bucketStart = start
- for (const frame of frames) {
- if (frame.t - bucketStart >= 5000) {
- out.push({ from: bucketStart - start, dts: bucket.map((f) => f.dt) })
- bucket = []
- bucketStart = frame.t
- }
- bucket.push(frame)
- }
- if (bucket.length) out.push({ from: bucketStart - start, dts: bucket.map((f) => f.dt) })
- return {
- windows: out.map((w) => ({ from: w.from, dts: w.dts })),
- longTasks: window.__t26.longTasks,
- blocked: window.__t26.blocked,
- }
- })
- console.log(`被拦截的 map-snapshot 请求数:${result.blocked}`)
- console.log('fps 窗口:')
- for (const w of result.windows) {
- const s = stats(w.dts)
- console.log(` ${(w.from / 1000).toFixed(1)}s p50=${s.p50} p95=${s.p95} max=${s.max}`)
- }
- const big = result.longTasks.filter((t) => t.dur >= 100)
- console.log(`长任务(≥100ms):${big.length} 个 ${JSON.stringify(big.slice(0, 8))}`)
- await browser.close()
|