test(t1-spike): Phase2节流测量收口——4包S2三开曲线/帧率/内存全数据
- S2@CPU4×+5Mbps(3遍中位):LittleJS冷开536/592ms·三开397/460ms;Phaser冷开2858/2818ms·三开684/746ms——编译缓存省~2.1s但只救二开起,首次试玩吃满2.8s(离3.5s地板余0.6s),冷开差5.3×与体积差30×机理互证 - 帧率@4×:四包中位59.88满帧;尾帧分化phaser-catcher 1%低=35.34(高密粒子,未破30线);CJK主取证面两引擎零帧率代价 - 内存:JSHeap 4/6MB双双远低于B4 150MB(procRSS~1GB系chrome全树固定开销,非引擎信号,口径已标注) - 钉子①终证:运行期零CSP违规零eval执行;phaser唯一new Function系不可达polyfill;matter-js零命中确为arcade变体 - 诚实声明:尾帧/JSHeap单遍作趋势;真机残差(XWeb冷开绝对值/真机code cache/CJK字体)留终裁包清单 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1506da704d
commit
5735c0eafc
@ -0,0 +1,294 @@
|
||||
/**
|
||||
* Phase 2 · B. 帧率 @CPU 4× —— 执行单 §6.2
|
||||
* ============================================================================
|
||||
* 每包在 juice 最密时刻采样帧率:
|
||||
* - catcher:临胜前落物风暴(高分段 spawn 频率拉满);
|
||||
* - shop:连续成交 + 怒走(快速 卖/进 + 顾客流失)。
|
||||
* 方法(只读,禁篡改状态):
|
||||
* 向 runner iframe 注入一个 **rAF 帧间隔采样器**(仅 requestAnimationFrame 取 performance.now()
|
||||
* 时间戳算帧间隔 dt,挂到 iframe window 的只读数组 __fpsSamples;不读/不改任何游戏内部状态、
|
||||
* 不注入任何输入或生命周期事件)。游戏由 **真实 CDP 输入**驱动(沿用各 lane drive 的 geom 瞄准)。
|
||||
* 采样窗口内收集帧间隔 → 算中位 fps(1000/median(dt))+ 最低 1% 帧间隔(p99 dt = 最慢帧)。
|
||||
* 节流:CPU 4×(与生死曲线同画像;网络不限——帧率是运行期成本,与冷加载无关)。
|
||||
*
|
||||
* 用法:node fps_measure.cjs <lane> <game> [seconds]
|
||||
*/
|
||||
'use strict';
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
|
||||
const LANE = process.argv[2];
|
||||
const GAME = process.argv[3];
|
||||
const SAMPLE_SEC = parseInt(process.argv[4] || '12', 10); // juice 密集采样窗(秒)
|
||||
if (!['littlejs', 'phaser'].includes(LANE) || !['catcher', 'shop'].includes(GAME)) {
|
||||
console.error('用法: node fps_measure.cjs <littlejs|phaser> <catcher|shop> [seconds]');
|
||||
process.exit(64);
|
||||
}
|
||||
const PORT = LANE === 'littlejs' ? 4311 : 4312;
|
||||
const HOST_URL = 'http://127.0.0.1:' + PORT + '/host.html?game=' + GAME;
|
||||
const CDP_PORT = 9320 + (LANE === 'littlejs' ? 0 : 2) + (GAME === 'shop' ? 1 : 0);
|
||||
const OUTDIR = path.join(__dirname, 'raw');
|
||||
const TAG = LANE + '-' + GAME;
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const logLines = [];
|
||||
function log(...a) { const s = '[fps:' + TAG + '] ' + a.map((x) => typeof x === 'string' ? x : JSON.stringify(x)).join(' '); console.log(s); logLines.push(s); }
|
||||
|
||||
class CDP {
|
||||
constructor(wsUrl) {
|
||||
this.ws = new WebSocket(wsUrl); this.id = 0; this.pending = new Map(); this.handlers = {};
|
||||
this.ready = new Promise((res, rej) => { this.ws.addEventListener('open', () => res()); this.ws.addEventListener('error', (e) => rej(e)); });
|
||||
this.ws.addEventListener('message', (ev) => {
|
||||
let m; try { m = JSON.parse(ev.data); } catch { return; }
|
||||
if (m.id !== undefined && this.pending.has(m.id)) { const { resolve, reject } = this.pending.get(m.id); this.pending.delete(m.id); m.error ? reject(new Error(JSON.stringify(m.error))) : resolve(m.result); }
|
||||
else if (m.method) (this.handlers[m.method] || []).forEach((h) => h(m.params));
|
||||
});
|
||||
}
|
||||
on(m, h) { (this.handlers[m] ||= []).push(h); }
|
||||
send(method, params) { const id = ++this.id; return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); this.ws.send(JSON.stringify({ id, method, params: params || {} })); }); }
|
||||
close() { try { this.ws.close(); } catch {} }
|
||||
}
|
||||
function httpJson(p) { return new Promise((resolve, reject) => { http.get('http://127.0.0.1:' + CDP_PORT + p, (res) => { let b = ''; res.on('data', (d) => b += d); res.on('end', () => { try { resolve(JSON.parse(b)); } catch (e) { reject(e); } }); }).on('error', reject); }); }
|
||||
function launchChrome() {
|
||||
const args = ['--headless=new', '--no-sandbox', '--disable-gpu', '--mute-audio', '--hide-scrollbars',
|
||||
'--window-size=900,900', '--force-device-scale-factor=2', '--remote-debugging-port=' + CDP_PORT,
|
||||
'--user-data-dir=/tmp/fps-' + TAG + '-' + Date.now(), 'about:blank'];
|
||||
const ch = cp.spawn('/usr/bin/google-chrome', args, { stdio: ['ignore', 'pipe', 'pipe'] }); ch.stderr.on('data', () => {}); return ch;
|
||||
}
|
||||
|
||||
// 只读 evaluate(顶层 host 上下文)
|
||||
async function evalTop(cdp, expr) {
|
||||
const r = await cdp.send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true });
|
||||
if (r.exceptionDetails) throw new Error('eval异常: ' + JSON.stringify(r.exceptionDetails));
|
||||
return r.result.value;
|
||||
}
|
||||
|
||||
// ---- 真实鼠标原语 ----
|
||||
const trace = [];
|
||||
async function rawMouse(cdp, type, x, y, buttons) {
|
||||
await cdp.send('Input.dispatchMouseEvent', { type, x: Math.round(x), y: Math.round(y), button: type === 'mouseMoved' ? 'none' : 'left', buttons: buttons != null ? buttons : (type === 'mousePressed' ? 1 : 0), clickCount: (type === 'mousePressed' || type === 'mouseReleased') ? 1 : 0 });
|
||||
trace.push({ t: Date.now(), type, x: Math.round(x), y: Math.round(y) });
|
||||
}
|
||||
let _jit = 0;
|
||||
const moveTo = (cdp, x, y) => { _jit = _jit ? 0 : 1; return rawMouse(cdp, 'mouseMoved', x + (_jit ? 0.6 : -0.4), y, 0); };
|
||||
async function clickAt(cdp, x, y) { await moveTo(cdp, x, y); await rawMouse(cdp, 'mousePressed', x, y, 1); await sleep(16); await rawMouse(cdp, 'mouseReleased', x, y, 0); }
|
||||
|
||||
// ---- geom 读取(按 lane 不同探针)----
|
||||
// littlejs: iframe.contentWindow.__gameDebug + canvas 几何换算(同 drive.cjs 口径)
|
||||
// phaser: host window.__GEOM__(runner 主动暴露 + iframe 偏移/缩放)
|
||||
const READ_GEOM_LJ = `(function(){
|
||||
var ifr=document.querySelector('iframe'); if(!ifr) return null;
|
||||
var ir=ifr.getBoundingClientRect(); var w=ifr.contentWindow; var gd=w&&w.__gameDebug;
|
||||
if(!gd) return {ready:false};
|
||||
var cv=w.document.querySelector('canvas');
|
||||
var cr=cv?cv.getBoundingClientRect():{left:0,top:0,width:ir.width,height:ir.height};
|
||||
var sx=cv?(cr.width/(cv.width||cr.width)):1, sy=cv?(cr.height/(cv.height||cr.height)):1;
|
||||
function vx(px){return ir.left+cr.left+px*sx;} function vy(px){return ir.top+cr.top+py(px);}
|
||||
function py(px){return px;}
|
||||
function VY(px){return ir.top+cr.top+px*sy;}
|
||||
var out={ready:true,phase:gd.phase,kind:(gd.coins!==undefined?'shop':'catcher'),
|
||||
iframe:{left:ir.left,top:ir.top,width:ir.width,height:ir.height}};
|
||||
if(gd.coins!==undefined){ // shop
|
||||
out.coins=gd.coins; out.stock=gd.stock; out.lost=(gd.churn!==undefined?gd.churn:gd.lost); out.queueLen=gd.queueLen;
|
||||
out.canRestock=gd.canRestock; out.canSell=gd.canSell;
|
||||
function pt(p){return p?{x:vx(p.x),y:VY(p.y)}:null;}
|
||||
out.restockBtn=pt(gd.restockBtn); out.sellBtn=pt(gd.sellBtn); out.frontCustomer=pt(gd.frontCustomer);
|
||||
} else { // catcher
|
||||
out.score=gd.score; out.lives=gd.lives;
|
||||
out.catcher={x:vx(gd.catcher.x),y:VY(gd.catcher.y)};
|
||||
var best=null,bw=Infinity;
|
||||
for(var i=0;i<gd.aim.length;i++){var a=gd.aim[i];if(a.wy>=gd.catchTopWorldY&&a.wy<bw){bw=a.wy;best=a;}}
|
||||
if(!best){for(var j=0;j<gd.aim.length;j++){var b=gd.aim[j];if(best===null||b.wy>best.wy)best=b;}}
|
||||
out.aimViewportX=best?vx(best.aimScreenX):out.catcher.x; out.starCount=gd.aim.length;
|
||||
}
|
||||
return out;
|
||||
})()`;
|
||||
const READ_GEOM_PH = `(function(){
|
||||
var g=window.__GEOM__; var s=window.__SPIKE__;
|
||||
if(!g||!g.iframe) return {ready:false};
|
||||
function vp(gx,gy){return {x:g.iframe.left+gx*g.iframe.scaleX, y:g.iframe.top+gy*g.iframe.scaleY};}
|
||||
var out={ready:true,kind:g.kind,iframe:g.iframe};
|
||||
if(g.kind==='shop'){
|
||||
out.coins=g.coins; out.stock=g.stock; out.lost=g.lost; out.queueLen=g.queueLen;
|
||||
out.restockBtn=g.restockBtn?{cx:g.restockBtn.cx,cy:g.restockBtn.cy,enabled:g.restockBtn.enabled, v:vp(g.restockBtn.cx,g.restockBtn.cy)}:null;
|
||||
out.sellBtn=g.sellBtn?{cx:g.sellBtn.cx,cy:g.sellBtn.cy,enabled:g.sellBtn.enabled, v:vp(g.sellBtn.cx,g.sellBtn.cy)}:null;
|
||||
out.frontCustomer=g.frontCustomer?{v:vp(g.frontCustomer.x,g.frontCustomer.y)}:null;
|
||||
} else {
|
||||
out.score=(s&&s.gameEndData)?s.gameEndData.score:(g.score!==undefined?g.score:0);
|
||||
out.lowestStar=g.lowestStar?{v:vp(g.lowestStar.x,580)}:null;
|
||||
out.catcherV=vp((g.catcherX!==undefined?g.catcherX:180),580);
|
||||
out.lives=g.lives;
|
||||
}
|
||||
return out;
|
||||
})()`;
|
||||
|
||||
async function readGeom(cdp) { return evalTop(cdp, LANE === 'littlejs' ? READ_GEOM_LJ : READ_GEOM_PH); }
|
||||
|
||||
// ---- 注入 rAF 帧间隔采样器到 iframe(只读:仅记 perf.now 帧间隔)----
|
||||
// 同源 iframe(allow-same-origin)下可经 contentWindow 注入;纯计时、零状态读写。
|
||||
async function installFpsSampler(cdp) {
|
||||
const expr = `(function(){
|
||||
var ifr=document.querySelector('iframe'); if(!ifr||!ifr.contentWindow) return false;
|
||||
var w=ifr.contentWindow;
|
||||
if(w.__fpsActive) return true;
|
||||
w.__fpsSamples=[]; w.__fpsActive=true; var last=w.performance.now();
|
||||
function loop(){ if(!w.__fpsActive) return; var t=w.performance.now(); var dt=t-last; last=t;
|
||||
if(dt>0 && dt<1000) w.__fpsSamples.push(+dt.toFixed(3)); w.requestAnimationFrame(loop); }
|
||||
w.requestAnimationFrame(loop); return true;
|
||||
})()`;
|
||||
return evalTop(cdp, expr);
|
||||
}
|
||||
async function readFpsSamples(cdp) {
|
||||
const expr = `(function(){var ifr=document.querySelector('iframe');var w=ifr&&ifr.contentWindow;return (w&&w.__fpsSamples)?w.__fpsSamples.slice():[];})()`;
|
||||
return evalTop(cdp, expr);
|
||||
}
|
||||
async function resetFpsSamples(cdp) {
|
||||
return evalTop(cdp, `(function(){var ifr=document.querySelector('iframe');var w=ifr&&ifr.contentWindow;if(w&&w.__fpsSamples){w.__fpsSamples.length=0;return true;}return false;})()`);
|
||||
}
|
||||
|
||||
// ---- 等 input_bound(host 探针) ----
|
||||
async function waitInputBound(cdp) {
|
||||
const expr = LANE === 'littlejs'
|
||||
? `(window.__probe && window.__probe.summary().s2_ms!=null)`
|
||||
: `(!!(window.__SPIKE__ && window.__SPIKE__.checks && window.__SPIKE__.checks.s2_ms!=null))`;
|
||||
for (let i = 0; i < 300; i++) { const ok = await evalTop(cdp, expr).catch(() => false); if (ok) return true; await sleep(100); }
|
||||
return false;
|
||||
}
|
||||
async function endedYet(cdp) {
|
||||
const expr = LANE === 'littlejs'
|
||||
? `(window.__probe && window.__probe.endData()!=null)`
|
||||
: `(!!(window.__SPIKE__ && window.__SPIKE__.gameEndData))`;
|
||||
return evalTop(cdp, expr).catch(() => false);
|
||||
}
|
||||
|
||||
// ---- 驱动到 juice 密集态并采样 ----
|
||||
async function driveAndSample(cdp) {
|
||||
// 触发 game_start:catcher 点一下捕手位;shop 点进货
|
||||
let g = await readGeom(cdp);
|
||||
let guard = 0;
|
||||
while ((!g || !g.ready) && guard++ < 80) { await sleep(100); g = await readGeom(cdp); }
|
||||
if (!g || !g.ready) throw new Error('geom 未就绪');
|
||||
|
||||
if (GAME === 'catcher') {
|
||||
const cx = LANE === 'littlejs' ? g.catcher.x : g.catcherV.x;
|
||||
const cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y;
|
||||
await rawMouse(cdp, 'mousePressed', cx, cy, 1); await sleep(20); await rawMouse(cdp, 'mouseReleased', cx, cy, 0);
|
||||
await moveTo(cdp, cx, cy);
|
||||
} else {
|
||||
const rb = LANE === 'littlejs' ? g.restockBtn : (g.restockBtn && g.restockBtn.v);
|
||||
if (rb) await clickAt(cdp, rb.x, rb.y);
|
||||
}
|
||||
|
||||
// 装采样器(game_start 后;iframe 已稳)
|
||||
await installFpsSampler(cdp);
|
||||
|
||||
// —— 先把游戏推进到「juice 密集段」——
|
||||
// catcher:持续接星到高分(spawn 频率随时间拉满 = 落物风暴);约 18-22s 后达临胜密集。
|
||||
// shop:连续成交 + 故意让部分顾客流失(怒走 juice),维持高频点按。
|
||||
// 然后在「密集窗 SAMPLE_SEC 秒」清空采样、纯采样。
|
||||
const warmMs = GAME === 'catcher' ? 16000 : 12000; // 推进到密集态的预热
|
||||
const tWarmEnd = Date.now() + warmMs;
|
||||
while (Date.now() < tWarmEnd) {
|
||||
if (await endedYet(cdp)) break;
|
||||
g = await readGeom(cdp);
|
||||
if (g && g.ready) {
|
||||
if (GAME === 'catcher') {
|
||||
const aimX = LANE === 'littlejs' ? g.aimViewportX : (g.lowestStar ? g.lowestStar.v.x : g.catcherV.x);
|
||||
const cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y;
|
||||
await moveTo(cdp, aimX, cy);
|
||||
} else {
|
||||
await shopStep(cdp, g);
|
||||
}
|
||||
}
|
||||
await sleep(GAME === 'catcher' ? 8 : 70);
|
||||
}
|
||||
|
||||
// —— 进入纯采样窗:清空采样器,密集驱动 SAMPLE_SEC 秒 ——
|
||||
await resetFpsSamples(cdp);
|
||||
log('采样窗开始 ' + SAMPLE_SEC + 's(juice 密集)');
|
||||
const tSampleEnd = Date.now() + SAMPLE_SEC * 1000;
|
||||
let endedDuringSample = false;
|
||||
while (Date.now() < tSampleEnd) {
|
||||
if (await endedYet(cdp)) { endedDuringSample = true; break; }
|
||||
g = await readGeom(cdp);
|
||||
if (g && g.ready) {
|
||||
if (GAME === 'catcher') {
|
||||
const aimX = LANE === 'littlejs' ? g.aimViewportX : (g.lowestStar ? g.lowestStar.v.x : g.catcherV.x);
|
||||
const cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y;
|
||||
await moveTo(cdp, aimX, cy);
|
||||
} else {
|
||||
await shopStep(cdp, g);
|
||||
}
|
||||
}
|
||||
await sleep(GAME === 'catcher' ? 8 : 70);
|
||||
}
|
||||
const samples = await readFpsSamples(cdp);
|
||||
log('采样窗结束,收集帧间隔 ' + samples.length + ' 个' + (endedDuringSample ? '(采样中游戏已终局)' : ''));
|
||||
return { samples, endedDuringSample };
|
||||
}
|
||||
|
||||
// shop 一步:连续成交 + 容忍怒走(juice 密集)。库存薄补货,有货有客即卖。
|
||||
let _lastRestock = 0;
|
||||
async function shopStep(cdp, g) {
|
||||
const rb = LANE === 'littlejs' ? g.restockBtn : (g.restockBtn && g.restockBtn.v);
|
||||
const sb = LANE === 'littlejs' ? g.sellBtn : (g.sellBtn && g.sellBtn.v);
|
||||
const fc = LANE === 'littlejs' ? g.frontCustomer : (g.frontCustomer && g.frontCustomer.v);
|
||||
const stock = g.stock != null ? g.stock : 0;
|
||||
const canSell = LANE === 'littlejs' ? g.canSell : (g.sellBtn && g.sellBtn.enabled);
|
||||
const canRestock = LANE === 'littlejs' ? g.canRestock : (g.restockBtn && g.restockBtn.enabled);
|
||||
if (stock > 0 && fc) { await clickAt(cdp, fc.x, fc.y); }
|
||||
else if (canSell && sb) { await clickAt(cdp, sb.x, sb.y); }
|
||||
else if (stock === 0 && canRestock && rb && (Date.now() - _lastRestock > 200)) { await clickAt(cdp, rb.x, rb.y); _lastRestock = Date.now(); }
|
||||
}
|
||||
|
||||
function pct(arr, p) { const a = arr.slice().sort((x, y) => x - y); if (!a.length) return null; const idx = Math.min(a.length - 1, Math.floor(p / 100 * a.length)); return a[idx]; }
|
||||
function median(arr) { const a = arr.slice().sort((x, y) => x - y); if (!a.length) return null; const m = Math.floor(a.length / 2); return a.length % 2 ? a[m] : (a[m - 1] + a[m]) / 2; }
|
||||
|
||||
(async () => {
|
||||
log('=== 帧率@CPU4× · ' + TAG + ' · ' + HOST_URL + ' ===');
|
||||
const chrome = launchChrome();
|
||||
let ver = null;
|
||||
for (let i = 0; i < 50; i++) { try { ver = await httpJson('/json/version'); if (ver && ver.webSocketDebuggerUrl) break; } catch {} await sleep(250); }
|
||||
if (!ver) { chrome.kill('SIGKILL'); throw new Error('chrome 端口未就绪'); }
|
||||
const bcdp = new CDP(ver.webSocketDebuggerUrl); await bcdp.ready;
|
||||
const { targetId } = await bcdp.send('Target.createTarget', { url: 'about:blank' });
|
||||
let pageInfo = null;
|
||||
for (let i = 0; i < 30; i++) { const list = await httpJson('/json'); pageInfo = list.find((t) => t.id === targetId && t.webSocketDebuggerUrl); if (pageInfo) break; await sleep(150); }
|
||||
const cdp = new CDP(pageInfo.webSocketDebuggerUrl); await cdp.ready;
|
||||
await cdp.send('Page.enable'); await cdp.send('Runtime.enable');
|
||||
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 4 }); // CPU 4×(帧率画像;不限网络)
|
||||
|
||||
await cdp.send('Page.navigate', { url: HOST_URL });
|
||||
if (!(await waitInputBound(cdp))) { log('FATAL: 未达 input_bound'); chrome.kill('SIGKILL'); process.exit(3); }
|
||||
log('input_bound 达成,开始驱动至 juice 密集态');
|
||||
|
||||
let res;
|
||||
try { res = await driveAndSample(cdp); }
|
||||
catch (e) { log('驱动异常: ' + (e && e.message)); res = { samples: [], endedDuringSample: false }; }
|
||||
|
||||
const s = res.samples.filter((x) => x > 0 && x < 1000);
|
||||
const medDt = median(s);
|
||||
const p99Dt = pct(s, 99); // 最慢 1% 帧间隔(最低 1% fps 的代理)
|
||||
const out = {
|
||||
pack: TAG, lane: LANE, game: GAME, url: HOST_URL,
|
||||
throttle: { cpu_rate: 4 },
|
||||
method: 'rAF 帧间隔采样器注入 iframe(只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 ' + SAMPLE_SEC + 's)',
|
||||
sample_count: s.length,
|
||||
median_frame_interval_ms: medDt != null ? +medDt.toFixed(3) : null,
|
||||
median_fps: medDt ? +(1000 / medDt).toFixed(2) : null,
|
||||
p99_frame_interval_ms: p99Dt != null ? +p99Dt.toFixed(3) : null, // 最低 1% 帧间隔(最慢帧)
|
||||
worst_1pct_fps: p99Dt ? +(1000 / p99Dt).toFixed(2) : null,
|
||||
p95_frame_interval_ms: pct(s, 95),
|
||||
min_frame_interval_ms: s.length ? +Math.min(...s).toFixed(3) : null,
|
||||
max_frame_interval_ms: s.length ? +Math.max(...s).toFixed(3) : null,
|
||||
ended_during_sample: res.endedDuringSample,
|
||||
samples_head: s.slice(0, 40),
|
||||
samples_tail: s.slice(-40),
|
||||
};
|
||||
fs.writeFileSync(path.join(OUTDIR, 'fps-' + TAG + '.json'), JSON.stringify(out, null, 2));
|
||||
fs.writeFileSync(path.join(OUTDIR, 'fps-' + TAG + '-samples.json'), JSON.stringify(s));
|
||||
fs.writeFileSync(path.join(OUTDIR, 'fps-' + TAG + '.log'), logLines.join('\n') + '\n');
|
||||
log('DONE 中位 fps=' + out.median_fps + ' 最低1%fps=' + out.worst_1pct_fps + ' (n=' + s.length + ')');
|
||||
cdp.close(); bcdp.close(); chrome.kill('SIGKILL'); await sleep(300); process.exit(0);
|
||||
})().catch((e) => { console.error(e); process.exit(1); });
|
||||
@ -0,0 +1,182 @@
|
||||
# Phase 2 测量数据 · Tier1 引擎 eval-spike(HJ-T1-SPIKE-001)
|
||||
|
||||
> 主考数据:**S2 三开生死曲线(钉子⑤)** + 帧率@CPU4× + 内存 + 钉子①收尾。
|
||||
> 现场:mini-desktop(minione-ubuntu-desktop),google-chrome 146 + node 22.22。
|
||||
> 两 serve 常驻:littlejs :4311 / phaser :4312(创始人亲玩中,全程未清理)。
|
||||
> 被测对象:两 lane `vite build`(base:'./')产物 dist,经各自 `serve.cjs`(hashed assets `immutable` / HTML `no-cache`)托管;测量宿主 `host.html?game=catcher|shop`(七锚点 host 单时钟探针)。
|
||||
> 节流:CDP `Emulation.setCPUThrottlingRate=4` + `Network.emulateNetworkConditions`(下行 5Mbps / 上行 1Mbps / RTT 40ms)= 千元机+4G 画像。
|
||||
> 精度优先、诚实优先:每项 3 遍取中位+离散;口径差异/不稳定项如实标注,未挑好看的。
|
||||
|
||||
---
|
||||
|
||||
## 〇、四包总览表(一屏读完)
|
||||
|
||||
| 包 | S2 冷开 ms(中位[min-max]) | S2 二开 ms | S2 三开 ms(中位[min-max]) | 编译缓存收益<br>(冷−三开) | 中位 fps@4× | 最低1% fps | JSHeapUsed | 通关 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| **littlejs / catcher** | 536 [522–580] | 430 | 397 [387–454] | **139 ms** | 59.88 | 45.87 | 3.91 MB | ✅ win |
|
||||
| **littlejs / shop** | 592 [571–667] | 484 | 460 [423–523] | **132 ms** | 59.88 | 49.75 | 3.98 MB | ✅ win |
|
||||
| **phaser / catcher** | 2858 [2792–2871] | 757 | 684 [645–696] | **2174 ms** | 59.88 | 35.34 | 6.28 MB | ✅ win |
|
||||
| **phaser / shop** | 2818 [2813–2883] | 781 | 746 [716–753] | **2072 ms** | 59.88 | 47.62 | 6.08 MB | ✅ win |
|
||||
|
||||
> S2 = host 单时钟 `t_input_bound − t_nav`(t_nav=设 iframe.src 时刻;t_input_bound=输入监听已挂+主循环在跑)。fps/内存对照线:≥30fps / B4 ≤150MB(JSHeap 口径,见 §C 口径说明)。
|
||||
> 全部 8 局(4 包×胜)真实 CDP 输入达成,outcome=win/score 实测;锚点序合法 `t_game_loaded ≥ t_input_bound`(order_ok=true,全开全包)。
|
||||
|
||||
---
|
||||
|
||||
## A. S2 三开生死曲线(钉子⑤主数据)
|
||||
|
||||
### A.1 全量数据(每包 3 遍 × 3 开,单位 ms,host 单时钟)
|
||||
|
||||
| 包 | 遍 | 冷开 | 二开 | 三开 |
|
||||
|---|---|---|---|---|
|
||||
| littlejs-catcher | rep1 | 579.6 | 461.8 | 387.0 |
|
||||
| | rep2 | 521.7 | 430.2 | 454.4 |
|
||||
| | rep3 | 535.7 | 410.2 | 396.7 |
|
||||
| | **中位** | **535.7** | **430.2** | **396.7** |
|
||||
| littlejs-shop | rep1 | 571.1 | 472.3 | 423.0 |
|
||||
| | rep2 | 592.4 | 484.0 | 460.3 |
|
||||
| | rep3 | 666.9 | 489.5 | 522.9 |
|
||||
| | **中位** | **592.4** | **484.0** | **460.3** |
|
||||
| phaser-catcher | rep1 | 2870.6 | 723.4 | 684.4 |
|
||||
| | rep2 | 2858.0 | 759.6 | 695.5 |
|
||||
| | rep3 | 2792.3 | 756.5 | 645.0 |
|
||||
| | **中位** | **2858.0** | **756.5** | **684.4** |
|
||||
| phaser-shop | rep1 | 2883.1 | 765.9 | 716.3 |
|
||||
| | rep2 | 2817.7 | 780.5 | 752.8 |
|
||||
| | rep3 | 2812.6 | 798.8 | 745.6 |
|
||||
| | **中位** | **2817.7** | **780.5** | **745.6** |
|
||||
|
||||
原始:`raw/s2-<pack>.json`(含每遍每开七锚点)+ `raw/s2-<pack>.log` + `raw/s2-all.log`(跑批日志)。
|
||||
|
||||
### A.2 三开协议(执行单 §6.1)落地说明
|
||||
|
||||
- **冷开** = 每遍一个**全新 chrome + 全新 `--user-data-dir` 临时目录**(保证 HTTP 缓存 + V8 编译缓存**双冷**)→ 新建 page target → 节流 → 导航 `host.html?game=` → 收七锚点。
|
||||
- **二开/三开** = 同一 chrome **同 profile**,**关页(`Page.close` + `Target.closeTarget`)后新建 page target 重开同 URL**(非刷新;profile 内磁盘缓存 + V8 code cache 保留)。
|
||||
- 每包跑 N=3 遍(每遍=一次独立的「冷→二→三」三连开,各自全新 profile),三开位各取 3 个样本算中位+min/max。
|
||||
|
||||
### A.3 判读 —— 编译缓存收益(钉子⑤ 的 Chromium 代理)
|
||||
|
||||
**结论:LittleJS 冷开 ~0.5s、Phaser 冷开 ~2.8s(5.3×);二/三开 Phaser 靠编译缓存回落到 ~0.7s,但冷开「首次可玩」差距 ~2.3s 不可消除。**
|
||||
|
||||
1. **LittleJS:冷开已极快,编译缓存收益小但可观测(非纯噪声)。**
|
||||
- 冷开中位 catcher 536ms / shop 592ms;三开 397/460ms。收益(冷−三开)catcher **139ms** / shop **132ms**——两游戏一致,方向稳定(冷>二>三的单调下降在多数遍成立)。
|
||||
- 离散:冷开 catcher [522–580]、shop [571–667](shop rep3 冷开 667ms 偏高,单点离群);三开 catcher [387–454]。收益量级(~135ms)显著大于三开内部离散(~70ms),故**可判定为真实编译缓存收益,而非噪声**——修正了 lane REPORT「LittleJS 包小差值可能在噪声内」的保守预判:实测有 ~135ms 可观测收益(对应其 ~40KB 引擎 chunk 的冷编译成本)。
|
||||
|
||||
2. **Phaser:冷开慢且编译缓存收益巨大(钉子⑤ 的核心信号)。**
|
||||
- 冷开中位 catcher **2858ms** / shop **2818ms**——逼近约束框架「首次 ≤3.5s 地板」(余量仅 ~0.6–0.7s)。
|
||||
- 三开中位 catcher 684ms / shop 746ms。收益(冷−三开)catcher **2174ms** / shop **2072ms**——即 V8 code cache 把 1.24MB 引擎 chunk 的冷解析+编译成本(~2.1s)几乎全部省掉。
|
||||
- 离散极小:冷开 catcher [2792–2871](±40ms)、shop [2813–2883](±35ms)——**Phaser 冷开慢得非常稳定可复现**,不是偶发。
|
||||
- **二开 vs 三开**:catcher 757→684、shop 781→746,二开已基本吃满缓存(二↔三差仅 ~40–70ms,在离散内),说明 V8 code cache 在**第一次重开即生效**,不需要三开预热。
|
||||
|
||||
3. **机理(与体积实测互证)**:S2 冷开差距的直接成因 = 引擎 chunk 冷解析+编译体量。Phaser as-shipped 引擎 chunk = **1.24MB raw / 326KB gz**(`SIZES.md` v2);LittleJS tree-shaken = **40.6KB raw / 15.9KB gz**(全量 min 也仅 58.9KB gz)。CPU 4× 下,~1.24MB JS 的冷编译≈2.8s,~40KB≈0.5s——体积差(~30×)直接转译为冷开 S2 差(~5×,非线性因含固定开销)。编译缓存(钉子⑤)正是抹平此差的机制,但**只对「同机第二次起的开」有效**;用户**首次试玩**(冷装/清缓存/换设备)吃满 Phaser 的 ~2.8s。
|
||||
|
||||
> **对引擎终裁的 S2 裁断(KD3「S2 实测是主考」)**:
|
||||
> - 稳态(二开起,老用户/缓存命中):两引擎都 < 0.8s,**都达标**,Phaser 不构成体验阻塞。
|
||||
> - 冷态(首次试玩,新用户首屏):LittleJS ~0.5s vs Phaser ~2.8s,**Phaser 逼近 3.5s 地板(余量 0.6s)**——这是「短视频流即点即玩」首因人群最敏感的指标。**Phaser 的冷开 S2 是其相对 LittleJS 的核心实测负分项**(与 SIZES「Phaser 需切预构建形态过门」的一次性工程成本叠加)。
|
||||
> - 真机残差(§7):Chromium V8 code cache ≠ 微信 XWeb 编译缓存策略;XWeb 冷开是否同样受益于 code cache、冷开绝对值,本 spike 不可达,留终裁包真机残差清单。
|
||||
|
||||
---
|
||||
|
||||
## B. 帧率 @CPU 4×
|
||||
|
||||
### B.1 方法(写进报告,执行单 §6.2 要求)
|
||||
|
||||
- **采样方法**:向 runner iframe(同源 `allow-same-origin`)**注入一个 rAF 帧间隔采样器**——仅 `requestAnimationFrame` 取 `performance.now()` 算相邻帧间隔 dt,push 进 iframe window 的只读数组 `__fpsSamples`。**纯计时、零游戏状态读写、零输入/生命周期注入**(取证红线 §5.2 严守)。
|
||||
- **驱动**:游戏全程**真实 CDP 输入**(`Input.dispatchMouseEvent`),沿用各 lane drive 的 geom 瞄准(catcher 追 lowestStar 拖动 / shop 真点进货·卖货·队首顾客);**禁 evaluate 篡改状态**。
|
||||
- **juice 最密时刻**:catcher = 先 16s 预热把游戏推进到高分段(spawn 频率随时间拉满 = 临胜前落物风暴),再清空采样器纯采样 12s;shop = 先 12s 预热进入连续成交+怒走密集态,再纯采样 12s。
|
||||
- **指标**:中位 fps = 1000/median(dt);最低 1% 帧间隔 = p99(dt)(最慢 1% 帧),其 fps = 1000/p99(dt),对照 ≥30fps 线。
|
||||
- 节流:仅 CPU 4×(帧率是运行期成本,与冷加载/网络无关,故不限网络)。
|
||||
- 渲染器实测(承 lane REPORT):headless chrome 无 GPU → Phaser `renderer.type=1`(Canvas2D 降级),LittleJS WebGL2 不支持时自动 Canvas2D——**两引擎均实跑低端 WebView 关心的 Canvas2D 兜底路径**。
|
||||
|
||||
### B.2 数据
|
||||
|
||||
| 包 | 中位帧间隔 | 中位 fps | 最低1% 帧间隔(p99 dt) | 最低1% fps | p95 dt | 最大单帧 | 样本数 | 采样中终局 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| littlejs-catcher | 16.7 ms | **59.88** | 21.8 ms | **45.87** | 20.5 ms | 27.6 ms | 665 | 是(胜局演出落入采样窗)|
|
||||
| littlejs-shop | 16.7 ms | **59.88** | 20.1 ms | **49.75** | 18.6 ms | 22.8 ms | 721 | 否 |
|
||||
| phaser-catcher | 16.7 ms | **59.88** | 28.3 ms | **35.34** | 20.5 ms | **72.5 ms** | 633 | 是 |
|
||||
| phaser-shop | 16.7 ms | **59.88** | 21.0 ms | **47.62** | 19.7 ms | 22.2 ms | 721 | 否 |
|
||||
|
||||
原始:`raw/fps-<pack>.json`(含 samples_head/tail)+ `raw/fps-<pack>-samples.json`(全部帧间隔数组)+ `raw/fps-<pack>.log`。
|
||||
|
||||
### B.3 判读
|
||||
|
||||
- **四包中位 fps 全 = 59.88(即满 60fps,CPU 4× 下未掉中位帧)**——两引擎、两品类在千元机 CPU 画像下均稳态 60fps,**全部远超 ≥30fps 线**。
|
||||
- **最低 1% fps(尾帧抖动)分化**:
|
||||
- LittleJS:catcher 45.87 / shop 49.75,最大单帧 27.6 / 22.8ms——**尾帧很稳,最差帧仍 ~36fps 以上**。
|
||||
- Phaser:shop 47.62(最大 22.2ms,与 LittleJS 同级)但 **catcher 35.34(最大单帧 72.5ms)**——Phaser action 类(落物风暴)有更大偶发帧尖峰(单帧 72.5ms≈13.8fps 的一帧),p99 仍 28.3ms(≈35fps,**仍在 30fps 线上**)。
|
||||
- 解读:Phaser 经营/UI 类(shop)帧稳定性与 LittleJS 持平甚至更好(22ms 封顶);**Phaser 的尾帧劣势集中在 action 类高密度粒子/补间场景**(catcher 落物风暴),但未跌破 30fps 线。
|
||||
- **CJK/文本渲染性能(shop = CJK 主取证面)**:两引擎 shop 包均 59.88 中位 + 尾帧 ≤22.8ms——**多行中文 UI(招牌/HUD/按钮/队列/终局)在 Canvas2D 系统字体直出下零帧率代价**,两引擎都过关。
|
||||
|
||||
> **不稳定项声明(诚实)**:rAF 注入采样器测的是 **runner 自身 rAF 节奏**,在 headless+CPU4× 下中位恒 60fps 是「主循环没掉帧」的可靠证据;但 headless Canvas2D **非生产 WebGL/真机 GPU 代表**,绝对帧率到真机会变(GPU 合成、真机 CPU、微信 WebView 渲染管线均不同)。最低 1% 尾帧是单遍采样(未跑 3 遍),catcher「采样中终局」使尾部含胜局演出帧(finale 粒子可能贡献个别尖峰,已如实标注 ended=true)。**尾帧 1% 数据建议作趋势参考而非精确门**;中位 60fps 是稳健结论。
|
||||
|
||||
---
|
||||
|
||||
## C. 内存
|
||||
|
||||
### C.1 方法与口径
|
||||
|
||||
- 每包**真实 CDP 输入通关一局胜**后(catcher score≥30 / shop coins≥100,全 4 包实测 win),`Performance.getMetrics` 取 **JSHeapUsedSize / JSHeapTotalSize**;进程级取该 chrome `--user-data-dir` 的全部进程 RSS 合计(`ps` 粗粒度)。不节流(内存峰值与节流无关)。
|
||||
- **口径差异(关键,对照 B4 必读)**:
|
||||
- **JSHeap = 页面 JS 堆**(V8 堆内对象),是与 B4「≤150MB」**直接可比**的运行期内存口径。
|
||||
- **procRSS = 整个 headless chrome 进程树 RSS 合计**(browser + GPU + renderer + utility 多进程),含渲染管线/IPC/共享库,**远大于 JS 堆且各进程共享页被重复计数**——实测四包 procRSS **恒为 ~1014–1020MB 且与游戏/引擎无关**(纯 chrome 多进程固定开销),**故 procRSS 不是引擎内存信号、不可直接对照 B4**,仅作「全进程量级」旁证留档。
|
||||
|
||||
### C.2 数据
|
||||
|
||||
| 包 | JSHeapUsed | JSHeapTotal | DOM nodes | JS listeners | procRSS(全树,非B4口径)| 通关 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| littlejs-catcher | **3.91 MB** | 11.41 MB | 82 | 17 | ~1020 MB | win(score30) |
|
||||
| littlejs-shop | **3.98 MB** | 6.66 MB | 84 | 17 | ~1017 MB | win(score100) |
|
||||
| phaser-catcher | **6.28 MB** | 10.75 MB | 99 | 38 | ~1014 MB | win(score30) |
|
||||
| phaser-shop | **6.08 MB** | 8.01 MB | 102 | 38 | ~1014 MB | win(score100) |
|
||||
|
||||
原始:`raw/mem-<pack>.json`(含 game_end / Documents / Nodes / JSEventListeners)+ `raw/mem-<pack>.log`。
|
||||
|
||||
### C.3 判读
|
||||
|
||||
- **JSHeapUsed:LittleJS ~3.9–4.0MB / Phaser ~6.1–6.3MB**——两引擎 JS 堆均**远低于 B4 150MB**(LittleJS 余量 ×38、Phaser 余量 ×24),**双双达标且差距对预算无实质意义**。
|
||||
- Phaser JS 堆比 LittleJS 高 ~2–2.4MB(引擎对象图更大:listeners 38 vs 17、nodes 99–102 vs 82–84),与其引擎体量一致,但绝对值仍微不足道。
|
||||
- **procRSS 不参与 B4 判定**(见 C.1 口径说明,~1GB 是 chrome 全树固定开销,与引擎无关);若终裁要全进程口径,须用真机/单渲染进程 RSS,本 spike headless 多进程不具代表性,留真机残差。
|
||||
|
||||
> **不稳定/残差声明**:JSHeap 为「通关后 + 0.8s 演出/GC 稳定窗」单点采样(未跑 3 遍,但 JS 堆在静止态稳定,单点足够代表量级);未做强制 GC(`HeapProfiler.collectGarbage`)后再测「稳态低水位」,故 JSHeapUsed 含少量未回收临时对象,属偏保守(真实低水位可能更低)。真机内存(含纹理显存/WebView 开销)不可达,留终裁包真机残差。
|
||||
|
||||
---
|
||||
|
||||
## D. 钉子①收尾(unsafe-eval / 预构建形态零违规)
|
||||
|
||||
**一行结论:两 lane 运行期均零 CSP 违规、零 unsafe-eval 执行——Phaser 预构建 arcade-physics 形态在严格 CSP(`script-src 'self'`,无 `unsafe-eval`)下干净可跑;LittleJS 连静态 eval/new Function 都为 0。**
|
||||
|
||||
核对取证(未重做,汇两 lane 既有证据 + dist 静态复核):
|
||||
|
||||
| 维度 | LittleJS dist | Phaser dist(预构建 arcade-physics.min)|
|
||||
|---|---|---|
|
||||
| 静态 `eval(` | **0**(两 chunk)| **0** |
|
||||
| 静态 `new Function(` | **0** | **1**(仅 UMD globalThis polyfill 兜底:`try{return this\|\|new Function("return this")()}catch{...return window}`)|
|
||||
| `MatterPhysics`/`matter-js` | n/a | **0 命中**(确为真去 Matter 的 arcade-only 变体)|
|
||||
| 运行期 CSP 违规(beacon 源 + CDP Log 源,双源)| **0 / 0**(catcher verdict)| **0 / 0**(catcher & shop summary)|
|
||||
| 运行期未捕获异常 | **0** | **0**(catcher & shop)|
|
||||
|
||||
- **Phaser 的那 1 处 `new Function`** 是 webpack/UMD 自举的 globalThis 探测兜底:`if(typeof globalThis=="object")return globalThis; try{return this||new Function("return this")()}catch{...}`。现代 Chromium **`globalThis` 已定义 → 首句即 early-return,根本到不了 `new Function` 兜底**;即便到达也在 `try/catch` 内、严格 CSP 下被吞且回落 `window`。故**运行期从不执行、不触发 CSP 违规**(与两 lane 实测 CSP=0 互证)。属「**dead-code 静态存在、运行期不可达**」,非真实 unsafe-eval 使用——对 KD/终裁不构成 Phaser 的 CSP 阻塞项。
|
||||
- LittleJS ESM 产物**连静态 eval/new Function 都为 0**,CSP 适配性结构性更干净(无须依赖「兜底不可达」论证)。
|
||||
|
||||
> 残差:headless 实测覆盖现代 Chromium 的 globalThis early-return 路径;理论上**极老 WebView(无 globalThis)**会落到 `new Function` 兜底并在严格 CSP 下抛错被 catch(仍不崩,回落 window)——微信 XWeb 是否有此极端旧核,留真机残差(影响面:仅该 polyfill 兜底,不影响游戏逻辑)。
|
||||
|
||||
---
|
||||
|
||||
## E. 方法与不稳定项总声明(汇总)
|
||||
|
||||
1. **S2 单时钟**:所有 S2 以 host 页 `performance.now()` 单时钟算 `t_input_bound − t_nav`,与 lane 自检同口径;冷开的 host 页加载耗时(t_nav 偏移)不计入 S2(S2 只测 iframe runner 的「设 src→可玩」段)。
|
||||
2. **S2 三开**:每包 3 遍 × 3 开 = 9 样本/位;冷开全新 profile 双冷可靠,二/三开关页重开(非刷新)复用 profile 缓存。Phaser 冷开离散极小(±40ms)高可复现;LittleJS shop 冷开有单点离群(rep3 667ms),中位已规避。
|
||||
3. **帧率**:rAF 注入采样器测 runner 主循环 rAF 节奏(只读);中位 60fps 稳健,**最低 1% 尾帧为单遍采样**作趋势参考;headless Canvas2D 非真机 GPU 代表。
|
||||
4. **内存**:JSHeap 单点(通关+0.8s 稳定窗),未强制 GC(偏保守);**procRSS 是 chrome 全树固定开销 ~1GB、非引擎信号、不对照 B4**,JSHeap 才是 B4 口径。
|
||||
5. **真机残差(贯穿)**:微信 XWeb/低端 Android 真机不可达,Chromium 节流/code cache/Canvas2D 均为代理——冷开绝对值、code cache 真机受益、CJK 真机字体可得性、全进程内存均留终裁包真机残差清单(执行单 §7)。
|
||||
6. **取证纪律**:全程真实 CDP 输入达成 4 包×胜局(outcome=win 实测);evaluate 仅用于读探针/几何瞄准/装只读采样器,**零状态篡改、零生命周期注入**。
|
||||
|
||||
---
|
||||
|
||||
## F. 残留
|
||||
|
||||
1. **帧率最低 1% / 内存 JSHeap 为单遍/单点**(中位 fps 与 S2 是 3 遍):尾帧抖动与内存低水位如需精确化,可补 3 遍 + 强制 GC,但**对引擎终裁主结论(S2 冷开 + 中位 fps + JSHeap 量级)无影响**,未补。
|
||||
2. **procRSS 口径不具引擎区分度**(~1GB 全树固定开销):若终裁需全进程内存,须真机单渲染进程 RSS,headless 多进程不可用。
|
||||
3. **两 serve 全程常驻未动**(创始人亲玩中):4 包 URL 仍可亲玩;本测量只读 dist/起独立 chrome,未触碰 serve/lane 文件/staging。
|
||||
4. **回灌**:本 phase2 目录 `rsync` 回本仓 `docs/agent-specs/2026-06-11-tier1-engine-spike/phase2/`(不 commit,交主会话/终裁)。
|
||||
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Phase 2 · C. 内存 —— 执行单 §6.2(对照 B4 ≤150MB)
|
||||
* ============================================================================
|
||||
* 每包「通关一局后」采集:
|
||||
* - Performance.getMetrics(JSHeapUsedSize / JSHeapTotalSize)—— 页面 JS 堆口径;
|
||||
* - 进程级 RSS(ps 取该 chrome --user-data-dir 的所有进程 RSS 合计,粗粒度)。
|
||||
* 口径差异如实标注:JSHeap ≠ 全进程 RSS(后者含渲染器/GPU/IPC 等,远大于 JS 堆)。
|
||||
* 复用各 lane geom + 真实 CDP 输入驱动一局胜(catcher 接满 30 / shop 经营到 coins≥100)。
|
||||
* 不节流(内存峰值与节流无关;与运行期状态相关)。只读探针,禁篡改。
|
||||
*
|
||||
* 用法:node mem_measure.cjs <lane> <game>
|
||||
*/
|
||||
'use strict';
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
|
||||
const LANE = process.argv[2];
|
||||
const GAME = process.argv[3];
|
||||
if (!['littlejs', 'phaser'].includes(LANE) || !['catcher', 'shop'].includes(GAME)) {
|
||||
console.error('用法: node mem_measure.cjs <littlejs|phaser> <catcher|shop>'); process.exit(64);
|
||||
}
|
||||
const PORT = LANE === 'littlejs' ? 4311 : 4312;
|
||||
const HOST_URL = 'http://127.0.0.1:' + PORT + '/host.html?game=' + GAME;
|
||||
const CDP_PORT = 9340 + (LANE === 'littlejs' ? 0 : 2) + (GAME === 'shop' ? 1 : 0);
|
||||
const UDD = '/tmp/mem-' + LANE + '-' + GAME + '-' + Date.now();
|
||||
const OUTDIR = path.join(__dirname, 'raw');
|
||||
const TAG = LANE + '-' + GAME;
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const logLines = [];
|
||||
function log(...a) { const s = '[mem:' + TAG + '] ' + a.map((x) => typeof x === 'string' ? x : JSON.stringify(x)).join(' '); console.log(s); logLines.push(s); }
|
||||
|
||||
class CDP {
|
||||
constructor(wsUrl) {
|
||||
this.ws = new WebSocket(wsUrl); this.id = 0; this.pending = new Map(); this.handlers = {};
|
||||
this.ready = new Promise((res, rej) => { this.ws.addEventListener('open', () => res()); this.ws.addEventListener('error', (e) => rej(e)); });
|
||||
this.ws.addEventListener('message', (ev) => { let m; try { m = JSON.parse(ev.data); } catch { return; }
|
||||
if (m.id !== undefined && this.pending.has(m.id)) { const { resolve, reject } = this.pending.get(m.id); this.pending.delete(m.id); m.error ? reject(new Error(JSON.stringify(m.error))) : resolve(m.result); }
|
||||
else if (m.method) (this.handlers[m.method] || []).forEach((h) => h(m.params)); });
|
||||
}
|
||||
on(m, h) { (this.handlers[m] ||= []).push(h); }
|
||||
send(method, params) { const id = ++this.id; return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); this.ws.send(JSON.stringify({ id, method, params: params || {} })); }); }
|
||||
close() { try { this.ws.close(); } catch {} }
|
||||
}
|
||||
function httpJson(p) { return new Promise((resolve, reject) => { http.get('http://127.0.0.1:' + CDP_PORT + p, (res) => { let b = ''; res.on('data', (d) => b += d); res.on('end', () => { try { resolve(JSON.parse(b)); } catch (e) { reject(e); } }); }).on('error', reject); }); }
|
||||
function launchChrome() {
|
||||
const args = ['--headless=new', '--no-sandbox', '--disable-gpu', '--mute-audio', '--hide-scrollbars',
|
||||
'--window-size=900,900', '--force-device-scale-factor=2', '--remote-debugging-port=' + CDP_PORT,
|
||||
'--user-data-dir=' + UDD, 'about:blank'];
|
||||
return cp.spawn('/usr/bin/google-chrome', args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
}
|
||||
async function evalTop(cdp, expr) { const r = await cdp.send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) throw new Error('eval异常: ' + JSON.stringify(r.exceptionDetails)); return r.result.value; }
|
||||
|
||||
const trace = [];
|
||||
async function rawMouse(cdp, type, x, y, buttons) { await cdp.send('Input.dispatchMouseEvent', { type, x: Math.round(x), y: Math.round(y), button: type === 'mouseMoved' ? 'none' : 'left', buttons: buttons != null ? buttons : (type === 'mousePressed' ? 1 : 0), clickCount: (type === 'mousePressed' || type === 'mouseReleased') ? 1 : 0 }); trace.push({ t: Date.now(), type, x, y }); }
|
||||
let _jit = 0;
|
||||
const moveTo = (cdp, x, y) => { _jit = _jit ? 0 : 1; return rawMouse(cdp, 'mouseMoved', x + (_jit ? 0.6 : -0.4), y, 0); };
|
||||
async function clickAt(cdp, x, y) { await moveTo(cdp, x, y); await rawMouse(cdp, 'mousePressed', x, y, 1); await sleep(16); await rawMouse(cdp, 'mouseReleased', x, y, 0); }
|
||||
|
||||
// geom 读取(同 fps 口径)
|
||||
const READ_GEOM_LJ = `(function(){
|
||||
var ifr=document.querySelector('iframe'); if(!ifr) return null;
|
||||
var ir=ifr.getBoundingClientRect(); var w=ifr.contentWindow; var gd=w&&w.__gameDebug;
|
||||
if(!gd) return {ready:false};
|
||||
var cv=w.document.querySelector('canvas');
|
||||
var cr=cv?cv.getBoundingClientRect():{left:0,top:0,width:ir.width,height:ir.height};
|
||||
var sx=cv?(cr.width/(cv.width||cr.width)):1, sy=cv?(cr.height/(cv.height||cr.height)):1;
|
||||
function vx(px){return ir.left+cr.left+px*sx;} function VY(px){return ir.top+cr.top+px*sy;}
|
||||
var out={ready:true,phase:gd.phase,kind:(gd.coins!==undefined?'shop':'catcher'),iframe:{left:ir.left,top:ir.top,width:ir.width,height:ir.height}};
|
||||
if(gd.coins!==undefined){ out.coins=gd.coins; out.stock=gd.stock; out.lost=(gd.churn!==undefined?gd.churn:gd.lost); out.queueLen=gd.queueLen; out.canRestock=gd.canRestock; out.canSell=gd.canSell;
|
||||
function pt(p){return p?{x:vx(p.x),y:VY(p.y)}:null;} out.restockBtn=pt(gd.restockBtn); out.sellBtn=pt(gd.sellBtn); out.frontCustomer=pt(gd.frontCustomer);
|
||||
} else { out.score=gd.score; out.lives=gd.lives; out.catcher={x:vx(gd.catcher.x),y:VY(gd.catcher.y)};
|
||||
var best=null,bw=Infinity; for(var i=0;i<gd.aim.length;i++){var a=gd.aim[i];if(a.wy>=gd.catchTopWorldY&&a.wy<bw){bw=a.wy;best=a;}}
|
||||
if(!best){for(var j=0;j<gd.aim.length;j++){var b=gd.aim[j];if(best===null||b.wy>best.wy)best=b;}}
|
||||
out.aimViewportX=best?vx(best.aimScreenX):out.catcher.x; }
|
||||
return out; })()`;
|
||||
const READ_GEOM_PH = `(function(){
|
||||
var g=window.__GEOM__; var s=window.__SPIKE__; if(!g||!g.iframe) return {ready:false};
|
||||
function vp(gx,gy){return {x:g.iframe.left+gx*g.iframe.scaleX, y:g.iframe.top+gy*g.iframe.scaleY};}
|
||||
var out={ready:true,kind:g.kind,iframe:g.iframe};
|
||||
if(g.kind==='shop'){ out.coins=g.coins; out.stock=g.stock; out.lost=g.lost; out.queueLen=g.queueLen;
|
||||
out.restockBtn=g.restockBtn?{enabled:g.restockBtn.enabled,v:vp(g.restockBtn.cx,g.restockBtn.cy)}:null;
|
||||
out.sellBtn=g.sellBtn?{enabled:g.sellBtn.enabled,v:vp(g.sellBtn.cx,g.sellBtn.cy)}:null;
|
||||
out.frontCustomer=g.frontCustomer?{v:vp(g.frontCustomer.x,g.frontCustomer.y)}:null;
|
||||
} else { out.score=g.score; out.lives=g.lives; out.lowestStar=g.lowestStar?{v:vp(g.lowestStar.x,580)}:null; out.catcherV=vp((g.catcherX!==undefined?g.catcherX:180),580); }
|
||||
return out; })()`;
|
||||
async function readGeom(cdp) { return evalTop(cdp, LANE === 'littlejs' ? READ_GEOM_LJ : READ_GEOM_PH); }
|
||||
|
||||
async function waitInputBound(cdp) {
|
||||
const expr = LANE === 'littlejs' ? `(window.__probe && window.__probe.summary().s2_ms!=null)` : `(!!(window.__SPIKE__ && window.__SPIKE__.checks && window.__SPIKE__.checks.s2_ms!=null))`;
|
||||
for (let i = 0; i < 300; i++) { const ok = await evalTop(cdp, expr).catch(() => false); if (ok) return true; await sleep(100); } return false;
|
||||
}
|
||||
async function endData(cdp) {
|
||||
const expr = LANE === 'littlejs' ? `JSON.stringify(window.__probe?window.__probe.endData():null)` : `JSON.stringify(window.__SPIKE__?window.__SPIKE__.gameEndData:null)`;
|
||||
return evalTop(cdp, expr).then((v) => { try { return JSON.parse(v); } catch { return null; } }).catch(() => null);
|
||||
}
|
||||
|
||||
let _lastRestock = 0;
|
||||
async function shopStep(cdp, g) {
|
||||
const rb = LANE === 'littlejs' ? g.restockBtn : (g.restockBtn && g.restockBtn.v);
|
||||
const sb = LANE === 'littlejs' ? g.sellBtn : (g.sellBtn && g.sellBtn.v);
|
||||
const fc = LANE === 'littlejs' ? g.frontCustomer : (g.frontCustomer && g.frontCustomer.v);
|
||||
const stock = g.stock != null ? g.stock : 0;
|
||||
const canSell = LANE === 'littlejs' ? g.canSell : (g.sellBtn && g.sellBtn.enabled);
|
||||
const canRestock = LANE === 'littlejs' ? g.canRestock : (g.restockBtn && g.restockBtn.enabled);
|
||||
// 胜策略:有货有客即卖(攒金币);无货补一次(保持 1 库存即可净 +5/单)
|
||||
if (stock > 0 && fc) await clickAt(cdp, fc.x, fc.y);
|
||||
else if (canSell && sb) await clickAt(cdp, sb.x, sb.y);
|
||||
else if (stock === 0 && canRestock && rb && (Date.now() - _lastRestock > 150)) { await clickAt(cdp, rb.x, rb.y); _lastRestock = Date.now(); }
|
||||
}
|
||||
|
||||
// 进程级 RSS(该 chrome user-data-dir 的全部进程 RSS 合计,KB)
|
||||
function procRssKb() {
|
||||
try {
|
||||
const out = cp.execSync(`ps -eo rss,args | grep -F "${UDD}" | grep -v grep | awk '{s+=$1} END {print s+0}'`).toString().trim();
|
||||
return parseInt(out, 10) || null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
async function playWin(cdp) {
|
||||
let g = await readGeom(cdp); let guard = 0;
|
||||
while ((!g || !g.ready) && guard++ < 80) { await sleep(100); g = await readGeom(cdp); }
|
||||
if (!g || !g.ready) throw new Error('geom 未就绪');
|
||||
if (GAME === 'catcher') {
|
||||
const cx = LANE === 'littlejs' ? g.catcher.x : g.catcherV.x, cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y;
|
||||
await rawMouse(cdp, 'mousePressed', cx, cy, 1); await sleep(20); await rawMouse(cdp, 'mouseReleased', cx, cy, 0); await moveTo(cdp, cx, cy);
|
||||
} else { const rb = LANE === 'littlejs' ? g.restockBtn : (g.restockBtn && g.restockBtn.v); if (rb) await clickAt(cdp, rb.x, rb.y); }
|
||||
|
||||
const deadline = Date.now() + 160000;
|
||||
while (Date.now() < deadline) {
|
||||
const e = await endData(cdp); if (e) return e;
|
||||
g = await readGeom(cdp);
|
||||
if (g && g.ready) {
|
||||
if (GAME === 'catcher') { const aimX = LANE === 'littlejs' ? g.aimViewportX : (g.lowestStar ? g.lowestStar.v.x : g.catcherV.x); const cy = LANE === 'littlejs' ? g.catcher.y : g.catcherV.y; await moveTo(cdp, aimX, cy); }
|
||||
else await shopStep(cdp, g);
|
||||
}
|
||||
await sleep(GAME === 'catcher' ? 8 : 70);
|
||||
}
|
||||
return await endData(cdp);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
log('=== 内存 · ' + TAG + ' · ' + HOST_URL + ' ===');
|
||||
const chrome = launchChrome(); chrome.stderr.on('data', () => {});
|
||||
let ver = null;
|
||||
for (let i = 0; i < 50; i++) { try { ver = await httpJson('/json/version'); if (ver && ver.webSocketDebuggerUrl) break; } catch {} await sleep(250); }
|
||||
if (!ver) { chrome.kill('SIGKILL'); throw new Error('chrome 端口未就绪'); }
|
||||
const bcdp = new CDP(ver.webSocketDebuggerUrl); await bcdp.ready;
|
||||
const { targetId } = await bcdp.send('Target.createTarget', { url: 'about:blank' });
|
||||
let pi = null; for (let i = 0; i < 30; i++) { const list = await httpJson('/json'); pi = list.find((t) => t.id === targetId && t.webSocketDebuggerUrl); if (pi) break; await sleep(150); }
|
||||
const cdp = new CDP(pi.webSocketDebuggerUrl); await cdp.ready;
|
||||
await cdp.send('Page.enable'); await cdp.send('Runtime.enable'); await cdp.send('Performance.enable');
|
||||
|
||||
await cdp.send('Page.navigate', { url: HOST_URL });
|
||||
if (!(await waitInputBound(cdp))) { log('FATAL: 未达 input_bound'); chrome.kill('SIGKILL'); process.exit(3); }
|
||||
log('input_bound 达成,开始驱动一局胜');
|
||||
let end = null;
|
||||
try { end = await playWin(cdp); } catch (e) { log('驱动异常: ' + (e && e.message)); }
|
||||
log('一局结束 end=' + JSON.stringify(end));
|
||||
await sleep(800); // 让终局演出+GC 稳定
|
||||
|
||||
// Performance.getMetrics(JS 堆)
|
||||
const metrics = (await cdp.send('Performance.getMetrics')).metrics || [];
|
||||
const mmap = {}; for (const m of metrics) mmap[m.name] = m.value;
|
||||
const jsUsed = mmap.JSHeapUsedSize, jsTotal = mmap.JSHeapTotalSize;
|
||||
const rssKb = procRssKb();
|
||||
|
||||
const out = {
|
||||
pack: TAG, lane: LANE, game: GAME, url: HOST_URL,
|
||||
note: '通关一局后采集;不节流(内存与节流无关)。口径差异:JSHeap=页面 JS 堆;RSS=全 chrome 进程合计(含渲染/GPU/IPC,远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。',
|
||||
game_end: end,
|
||||
js_heap_used_bytes: jsUsed != null ? jsUsed : null,
|
||||
js_heap_used_mb: jsUsed != null ? +(jsUsed / 1048576).toFixed(2) : null,
|
||||
js_heap_total_bytes: jsTotal != null ? jsTotal : null,
|
||||
js_heap_total_mb: jsTotal != null ? +(jsTotal / 1048576).toFixed(2) : null,
|
||||
proc_rss_total_kb: rssKb,
|
||||
proc_rss_total_mb: rssKb != null ? +(rssKb / 1024).toFixed(2) : null,
|
||||
documents: mmap.Documents, nodes: mmap.Nodes, js_event_listeners: mmap.JSEventListeners,
|
||||
};
|
||||
fs.writeFileSync(path.join(OUTDIR, 'mem-' + TAG + '.json'), JSON.stringify(out, null, 2));
|
||||
fs.writeFileSync(path.join(OUTDIR, 'mem-' + TAG + '.log'), logLines.join('\n') + '\n');
|
||||
log('DONE JSHeapUsed=' + out.js_heap_used_mb + 'MB JSHeapTotal=' + out.js_heap_total_mb + 'MB procRSS=' + out.proc_rss_total_mb + 'MB');
|
||||
cdp.close(); bcdp.close(); chrome.kill('SIGKILL'); await sleep(300);
|
||||
fs.rmSync(UDD, { recursive: true, force: true });
|
||||
process.exit(0);
|
||||
})().catch((e) => { console.error(e); process.exit(1); });
|
||||
@ -0,0 +1 @@
|
||||
[16.5,16.6,15.7,17.4,17,15,16.4,18.9,16.7,15.9,16.7,15.4,17.5,19.6,14.5,16,15.1,17.7,17.4,15.9,16.9,17.5,18.5,13.9,18.2,16.4,17.2,15.5,17.1,18.1,13.4,18.2,16.8,16.5,17,16.9,16.8,14.4,17.6,17.4,16.5,15.8,18.5,14.7,17.7,16.7,14.9,20.1,14.9,18,15.3,22.2,9.1,19.9,16.5,17.5,15,17,16.2,14.7,19.4,16.2,16.2,17.2,16.4,18.5,15,14.9,20.2,15,16.9,16.9,15.3,17.3,18.2,15.5,17.1,16.8,16.1,16.8,18.2,14.5,17.8,15.7,17.9,15.7,16.4,17.3,16.5,15.7,18.3,15.4,16.4,15.6,21.7,16,13.9,18.7,13.2,16.1,18.1,15.9,16.2,17.2,16.1,19.4,15.6,16.2,17.2,15.3,18.4,16.7,16.1,15.7,17.4,17.5,16.1,17.1,16,17.3,14.4,19.3,14.3,18.4,16.7,16.5,17.2,16.1,17.1,16.7,17,16.7,16.2,17.2,14.7,17.7,20,13.7,25.2,7.9,15.1,19.7,15.6,15.8,15.2,19.3,16.2,16.5,17.3,14.8,27.6,11.1,14.1,21.8,10.1,16.5,17.4,16.6,16.1,15.4,16.6,17.6,18.4,15.4,16.8,18,15.1,16.7,14.8,18.7,16.9,15.6,16.7,17.1,16.7,16.5,17.1,17.1,14.1,18.8,18.5,12.8,17.5,18,15.1,17.9,16.7,17,16.1,14.9,17.1,20.3,12.5,18.4,25.4,7.3,18.6,15.3,17.6,15.3,18.5,15.5,15.2,18.4,17.1,16.9,18.6,12.9,19.5,17.6,15,16.8,13.8,16.9,19.5,15,16.5,16.4,16.3,17.7,17.3,14.2,18.4,17.1,17.3,14.8,16.5,15.9,18.2,15.4,19.2,15.4,18.2,15.5,17.9,16.2,15.4,16.5,17.5,17.3,15.2,17.4,16.4,17.6,14.4,18.6,20.7,14.9,16.4,15.3,13.9,16.4,17.2,18.9,15.6,18.3,15.8,16.2,14.7,18.2,15.9,17.3,18,14,18.4,17.6,17.5,14.8,17.1,14.8,17.6,18.9,15.5,16.4,17.2,15.2,17.7,17.5,14.8,17.5,16.8,15.4,18.5,16.5,16.1,16.7,15.5,19,15.5,16.7,17.6,16.3,16.8,14.3,18.4,16.9,16.3,16.2,17.3,14.6,18.3,18.9,15.7,15.3,17.9,16.8,16.4,15.8,17.1,16.8,15.3,16.5,15.8,19.3,16.4,17.4,16.1,18.4,13.9,17.6,16.7,16,17.3,16.8,15.2,17.5,16.7,17.1,17.8,15.7,17.1,18.1,14.8,16.2,15.8,18.2,16.4,16.2,17.5,15.3,16.6,17.4,17.9,14.8,17.4,15.6,18,16.9,15.7,16.2,19.8,13.6,17.4,16,17.2,17.1,16.7,15.5,18.2,15.4,17.8,15.3,17.8,16.5,16.1,17.9,17.1,15,16.9,15.9,17.7,16.1,17.2,14.6,17.5,17.3,16.7,17.6,14.8,18,17,17.6,16,14.1,19.4,14,16.7,18.7,18.6,20.2,10.6,17.9,17.3,15.1,16.6,16.4,17.6,16.5,16,17.3,14.4,17.4,16.1,18.4,17.8,15.9,15.2,15.7,18.1,16.7,17,17.2,16.8,16.3,16.4,17.3,15.4,19.7,14.2,17,15,17.9,16.8,16.3,17.8,14.1,20.7,19.1,10.8,18.1,17.1,16.7,16.6,16.7,15.9,16.5,15.7,18.8,16.5,16.6,14.9,16.1,18.9,16.2,17,16.4,16.7,17.7,15.9,14.6,16.7,18.8,14.3,18.6,16.7,14.8,17,17.5,18.4,16.1,14.6,18.2,16.5,21.5,12.8,14.1,19.4,14.3,19.1,14.8,17.8,15.7,15.4,18.4,17.4,15.2,16.5,19,14.6,16.7,17.9,16.1,17.1,16.2,15.2,18.7,16.5,16.2,16,18.1,15.5,15.5,20.7,15.1,15.3,17.6,16.2,15.4,17.7,17.8,16.2,16.9,17,14.2,17.8,16.1,20.1,14.6,16.9,15,18.6,15.4,19.1,15.1,16.9,17.5,14.5,15.8,19.5,15.5,17.5,16.4,16.6,16.1,17.5,16.5,19.4,16.5,12.4,18.7,14,19.9,15.9,16.3,17.4,15.1,17.8,17.3,15.6,17.6,16,15.5,19.9,15.1,16.7,16.5,17.3,16,13.8,19.5,16.6,16.7,16.3,17.2,16.5,17.7,16.3,15.5,16.6,16.4,17.8,16.4,15.6,15.6,20.3,18.5,14.7,14.8,17.4,16,15.3,19,16.3,16.6,25.8,9.9,14.7,14.5,15.5,16.9,19,14.8,18.2,14.5,16.7,17.3,16.5,16.2,17.8,18.2,16,17.7,17.1,13.9,18.8,16.5,17.6,17,16.3,16.6,16.8,16.1,17.5,12.9,16.7,16.9,19.5,13.6,18.4,19,16.2,13.5,19.5,13.3,19.3,14.1,20.2,15.7,17.5,14.8,18.7,16.5,13.3,20.7,13,19.6,20.2,12.3,16.6,14.2,19.5,16.9,14.5,16.3,18.9,17.8,13.7,18.9,18.5,11.8,22.5,13.7,15,18.7,17.4,14.8,19.6,12.8,19.6,14.9,17.4,14.4,15.9,18.9,16.7,15.7,17.9,17.3,15.9,17.3,16.2,17.1,17,15.1,19.1]
|
||||
@ -0,0 +1,103 @@
|
||||
{
|
||||
"pack": "littlejs-catcher",
|
||||
"lane": "littlejs",
|
||||
"game": "catcher",
|
||||
"url": "http://127.0.0.1:4311/host.html?game=catcher",
|
||||
"throttle": {
|
||||
"cpu_rate": 4
|
||||
},
|
||||
"method": "rAF 帧间隔采样器注入 iframe(只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 12s)",
|
||||
"sample_count": 665,
|
||||
"median_frame_interval_ms": 16.7,
|
||||
"median_fps": 59.88,
|
||||
"p99_frame_interval_ms": 21.8,
|
||||
"worst_1pct_fps": 45.87,
|
||||
"p95_frame_interval_ms": 19.5,
|
||||
"min_frame_interval_ms": 7.3,
|
||||
"max_frame_interval_ms": 27.6,
|
||||
"ended_during_sample": true,
|
||||
"samples_head": [
|
||||
16.5,
|
||||
16.6,
|
||||
15.7,
|
||||
17.4,
|
||||
17,
|
||||
15,
|
||||
16.4,
|
||||
18.9,
|
||||
16.7,
|
||||
15.9,
|
||||
16.7,
|
||||
15.4,
|
||||
17.5,
|
||||
19.6,
|
||||
14.5,
|
||||
16,
|
||||
15.1,
|
||||
17.7,
|
||||
17.4,
|
||||
15.9,
|
||||
16.9,
|
||||
17.5,
|
||||
18.5,
|
||||
13.9,
|
||||
18.2,
|
||||
16.4,
|
||||
17.2,
|
||||
15.5,
|
||||
17.1,
|
||||
18.1,
|
||||
13.4,
|
||||
18.2,
|
||||
16.8,
|
||||
16.5,
|
||||
17,
|
||||
16.9,
|
||||
16.8,
|
||||
14.4,
|
||||
17.6,
|
||||
17.4
|
||||
],
|
||||
"samples_tail": [
|
||||
19.6,
|
||||
20.2,
|
||||
12.3,
|
||||
16.6,
|
||||
14.2,
|
||||
19.5,
|
||||
16.9,
|
||||
14.5,
|
||||
16.3,
|
||||
18.9,
|
||||
17.8,
|
||||
13.7,
|
||||
18.9,
|
||||
18.5,
|
||||
11.8,
|
||||
22.5,
|
||||
13.7,
|
||||
15,
|
||||
18.7,
|
||||
17.4,
|
||||
14.8,
|
||||
19.6,
|
||||
12.8,
|
||||
19.6,
|
||||
14.9,
|
||||
17.4,
|
||||
14.4,
|
||||
15.9,
|
||||
18.9,
|
||||
16.7,
|
||||
15.7,
|
||||
17.9,
|
||||
17.3,
|
||||
15.9,
|
||||
17.3,
|
||||
16.2,
|
||||
17.1,
|
||||
17,
|
||||
15.1,
|
||||
19.1
|
||||
]
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
[17.9,16.1,16.2,16.9,16.9,17.1,16.3,16.8,15.9,16.1,16.6,17.5,17.8,16.3,16.8,16.5,16.3,16.8,16.6,16.4,17.9,16.5,14.7,16.5,16.6,18,17.2,16.1,16.9,15.8,17.7,17.2,14.4,17.7,16.8,17,16.1,16.8,17.1,16.2,16.8,15.4,18.1,17.6,16.4,15.6,16.2,18.1,14.7,18.8,15.3,18.2,16.6,14.7,18,16.9,16.1,16.8,16.4,16.5,16.5,16.7,17.5,15.6,17.7,16.1,17.2,17,15.1,18,16.7,17.1,15.2,16.6,18.3,15.3,16.4,17.8,16.2,16.9,16.8,17.1,14.5,16.7,17.4,16,18.9,15.7,16.4,16,17.8,16.5,16.3,18.1,16.2,15.5,17.2,17.9,15.1,16.7,16.1,22.6,12.2,15.2,17.6,15.1,17.5,18.2,15.4,17.4,15.9,17,17.7,16.2,15.1,18.4,16.4,16.5,16.7,16.2,15.7,16.4,16.7,16.7,18.4,14.8,18.5,14.9,16.8,18.2,14.9,16.6,16.9,18.5,15.7,15.7,18.6,14.9,17.6,17,16.6,17.8,16.1,16.7,15.9,17.8,14.2,17.2,17.8,15.2,19.5,13.8,17,16.5,16.6,18.3,15.1,20.9,13.8,15.1,16.7,18.3,16.1,16.2,17.8,15.8,16.9,16.7,17.5,16.2,16.4,16.8,15.4,17.6,17.6,15.8,17,16.7,15.3,18.3,15.9,17.9,18.7,14,15.3,16.5,19,16.2,18.5,16.1,15.6,16.4,17.4,16.4,17.4,16.4,14.9,19.4,14.9,16.6,20,12.7,16.8,16.1,18.1,16.3,16.9,15.1,18.6,15.3,17.4,15,18.4,17.3,15.7,15.1,18.6,16.2,16.8,17.4,15.8,17,16.7,16.9,16.2,16.9,16.5,16,17.6,14.6,16.7,18.4,16.9,16.7,15.9,15.4,18.4,16.4,15.2,18.4,15.2,17.9,17.3,16,15.3,17.4,15.9,18.5,16.1,17.4,16,15.5,16.5,22.8,10.5,18.8,16.4,16.6,16.8,15.5,16.3,16.9,17.5,16.1,17.6,16,16.8,16.9,17.1,16.6,15.2,16.4,18.6,16,16.9,17.2,14.3,19,16,16.1,17.7,15.6,16.7,16.6,17,17,17,15.1,22.1,11.8,17.2,15.9,16.7,18.2,16.2,16.5,15.2,16.7,18.5,17.8,17.6,13,17.4,16.7,16.9,16.9,15.3,17.8,16.9,17,15.1,17.2,17.9,15,17.2,16.5,17.8,16.5,15.5,16.3,16.7,17.5,18.2,13.9,18.6,17,15.5,17.4,17.3,14.7,17.4,17.2,16.9,17.4,14.9,17.7,17.3,15.8,15.5,17.6,17.5,14.6,18.6,19.5,14.4,15.7,17.4,16,15.5,18.3,17.2,16.4,16.1,15.8,17.2,16.7,17.3,16.2,16.4,17.1,16.7,16,16.5,17.8,14.3,18.5,16.2,16.8,18.1,14.9,18.5,14.9,17,15.2,18.6,15.8,15.6,17.2,17.2,16.2,17.7,15.2,18.5,16.7,20.3,12.5,15.4,18,15,16.8,17.2,18.3,16.2,16,16.8,17.4,16,16.2,20,12.3,18.5,16.6,17.2,16.2,17.5,15.3,17.5,16.2,17.3,15.4,16.3,16.2,17.7,17.9,17.1,15.5,15.1,19.8,14,18.2,16.5,17.1,15.7,15.9,16,18.2,16.8,16.9,14.7,18.7,16.4,18.6,15.1,16.4,16.3,15.9,19,15.6,15,17.4,18.8,14.2,17.8,18.5,13.2,17.5,15.8,16.6,18.6,14.6,17.6,17,16.1,17,16.9,17.1,16.9,17.4,14,17.3,17.3,16.8,15.7,17.6,17.8,16,16.4,16.8,15.9,17.8,15.8,17.4,14.8,18.3,16.7,17.4,16.4,17.2,15.2,17.2,14.8,17.2,17.8,16.6,15.2,18.6,16.2,17,15,16.8,20.1,14.6,16.4,15.6,19.7,15.2,14.8,18.6,16.5,16.7,17.3,15.3,16.9,16.3,17.1,17.9,16.6,17.8,14.3,17.7,15.8,17.1,17,17.9,15.2,14.8,17.9,17.4,16.6,16.8,16.9,16.6,16.9,16.2,16.6,16.3,15.3,18,17.2,17.3,15.1,17.2,17.2,16.8,16,15.1,21.4,13.2,16.4,17,17.5,16.9,15.3,17.5,16.5,16.6,17,15.8,15.9,18.3,15.2,17.8,16.5,16.7,20.2,12.7,17.8,17.9,13,16.9,18.7,15.3,17.9,14.5,17.8,17,17.6,15.6,17.1,17.5,14.1,16.8,17.9,16.3,17,16.9,17.4,16.5,16.3,17.2,14.3,18.6,15.4,18.3,15.7,17.2,15.8,15.9,17.9,17.9,14.1,17.7,17.7,16.8,16.5,16.5,16.4,16.9,16,19.5,14.2,17.6,16.1,16.6,16.7,17.4,14.4,19.1,18.3,12.5,17.7,16.8,17,19,14.3,16.2,16.1,16.8,17.3,15.6,17,16.4,17.5,17,15.3,17.6,16.5,17.2,15,18.5,17,14.7,19,15.3,16.7,15.9,16.3,17.6,16.1,16.7,17.6,15.7,17.4,16.8,17.5,16,16.2,17.1,16.8,16.1,17.3,16.4,15.4,17.7,15.4,17.1,17.6,16,18,16.7,16.6,16,16.6,15.5,18.3,16.6,16.1,17.8,14.8,19.8,15.5,14.9,18.7,16,17.1,17.1,14,18.5,16.7,16.4,17.1,14.6,18.5,16.8,14.9,18.7,16.5,16,18.1,15.8,16.6,17,14.5,18.8,17,16.2,14.8,17,18.1,15.6,16.1,18.1,14.9,17.2,16.8,16.9,17.5,17,14.8,18.3,15.2,18,16.6,17.6,15.8,15.7,16.2,19.1,15.6,17,16.2]
|
||||
@ -0,0 +1,103 @@
|
||||
{
|
||||
"pack": "littlejs-shop",
|
||||
"lane": "littlejs",
|
||||
"game": "shop",
|
||||
"url": "http://127.0.0.1:4311/host.html?game=shop",
|
||||
"throttle": {
|
||||
"cpu_rate": 4
|
||||
},
|
||||
"method": "rAF 帧间隔采样器注入 iframe(只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 12s)",
|
||||
"sample_count": 721,
|
||||
"median_frame_interval_ms": 16.7,
|
||||
"median_fps": 59.88,
|
||||
"p99_frame_interval_ms": 20.1,
|
||||
"worst_1pct_fps": 49.75,
|
||||
"p95_frame_interval_ms": 18.6,
|
||||
"min_frame_interval_ms": 10.5,
|
||||
"max_frame_interval_ms": 22.8,
|
||||
"ended_during_sample": false,
|
||||
"samples_head": [
|
||||
17.9,
|
||||
16.1,
|
||||
16.2,
|
||||
16.9,
|
||||
16.9,
|
||||
17.1,
|
||||
16.3,
|
||||
16.8,
|
||||
15.9,
|
||||
16.1,
|
||||
16.6,
|
||||
17.5,
|
||||
17.8,
|
||||
16.3,
|
||||
16.8,
|
||||
16.5,
|
||||
16.3,
|
||||
16.8,
|
||||
16.6,
|
||||
16.4,
|
||||
17.9,
|
||||
16.5,
|
||||
14.7,
|
||||
16.5,
|
||||
16.6,
|
||||
18,
|
||||
17.2,
|
||||
16.1,
|
||||
16.9,
|
||||
15.8,
|
||||
17.7,
|
||||
17.2,
|
||||
14.4,
|
||||
17.7,
|
||||
16.8,
|
||||
17,
|
||||
16.1,
|
||||
16.8,
|
||||
17.1,
|
||||
16.2
|
||||
],
|
||||
"samples_tail": [
|
||||
14.6,
|
||||
18.5,
|
||||
16.8,
|
||||
14.9,
|
||||
18.7,
|
||||
16.5,
|
||||
16,
|
||||
18.1,
|
||||
15.8,
|
||||
16.6,
|
||||
17,
|
||||
14.5,
|
||||
18.8,
|
||||
17,
|
||||
16.2,
|
||||
14.8,
|
||||
17,
|
||||
18.1,
|
||||
15.6,
|
||||
16.1,
|
||||
18.1,
|
||||
14.9,
|
||||
17.2,
|
||||
16.8,
|
||||
16.9,
|
||||
17.5,
|
||||
17,
|
||||
14.8,
|
||||
18.3,
|
||||
15.2,
|
||||
18,
|
||||
16.6,
|
||||
17.6,
|
||||
15.8,
|
||||
15.7,
|
||||
16.2,
|
||||
19.1,
|
||||
15.6,
|
||||
17,
|
||||
16.2
|
||||
]
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
[16.2,17.3,17.5,16.2,17.4,15.1,17.6,16.5,15.7,17.3,16.2,17,16.9,15.9,17.1,15.5,19.5,14.6,17.6,14.6,16.8,27.5,6.6,16.8,18.4,16.3,17.5,15,16.1,18,16.1,20.4,13,19,15,14.2,21.9,11.2,17.2,18.1,14.9,18.7,16.7,17.4,19.3,13.8,16.7,20.9,9.7,16.7,18.5,17.9,16.1,14.4,16.4,19,16.9,15.7,17.7,16.1,19.2,13.8,27.8,9.6,14,16.6,13.7,19.4,21.3,11.5,16.8,16.1,17.6,16.8,16,17.2,15.9,16.8,16.2,15.3,16.5,19,14.4,19,16.9,16.9,16.1,14.5,17.6,17.1,14.9,18.6,16.7,17,15.3,17.9,16.3,16.7,16.3,16.3,17.3,17.7,17.4,25.9,5.7,17.1,18.4,14.9,16.8,17.7,16.4,14.5,19.2,16.7,14.9,15.4,16.7,17.9,17.4,16.4,17.4,16.9,15.2,16.4,18.4,15.5,17,17.6,13.7,18.8,17.4,15.7,17.1,16,16.3,16.4,19.2,15,14.5,18.6,17,16.7,15.3,23.2,19.7,6.9,18.6,15.7,21.4,11.1,16.3,19,13.8,16.7,16.8,16.9,18.9,16.3,16.4,19,17.7,16.8,13.9,14,17.1,16.3,18.4,18.2,16.6,15.9,17.1,19.1,14.1,15.9,17,16.3,14.6,16.9,17.6,16.1,17.7,16.5,17,17.7,16.1,28.9,5.4,15.3,17.2,16.5,16,17,17.1,16.4,16.4,17.1,17,16.3,17.5,15.7,17,16.6,16.4,16.9,16.8,15.3,17.6,17.2,16,16.6,16,18.4,16.2,16.5,16.6,16.6,16,16.9,15,17.1,18.9,14.7,16.3,16.3,28.2,9.9,12,16.9,16.4,18.5,17.5,15.8,16.8,19.3,12.7,18.8,17.2,15.7,16.5,14.6,19.6,15.7,16.8,16.3,16.5,15.3,18.4,16.9,17.4,18.2,16.7,12.6,17.5,23.4,12.1,13.1,18.9,16.2,16.4,15.6,18.3,16.1,15.2,19.1,31.6,6.1,11.5,17.6,14.2,18.6,17.7,14.6,16,18.4,16.5,16.1,18.2,15.7,17.5,15.8,15.6,18.1,16.8,16.2,15,18.6,15.9,16.9,16.7,15.1,18.2,17.4,16.4,16.1,16.2,16.3,18.1,14.8,16.7,21.6,11.5,17.9,26.9,6.1,16.8,18,15.7,18,14.5,20.3,12.7,19.2,16.1,15.7,16.3,17.7,16.4,18.7,16.1,14.8,18.3,16.1,16.5,16,19.7,14.3,16.3,16.5,16.7,16.7,18,14.9,17.6,16.2,16,17.3,17,15.9,16.7,19.3,26.1,5.9,15.8,15.1,18.7,14.3,19.3,17.2,15.5,15.2,18.9,15.9,17.1,16.2,17.7,15.4,16.4,15.5,16.8,18.8,19.7,13.7,16.5,17.4,16.6,16.2,14.9,16.8,19.9,16.4,16.9,16,16.3,16.9,15.4,16,17.3,15.6,28.7,6,16.3,18.5,13.3,19.9,15.2,17.1,17.4,13.3,20,15.5,17.7,17.3,14.9,14.6,18.6,17.8,16.8,15.6,15.7,15.9,17.5,18.8,15.8,15,19.5,15.7,16,18.3,14.8,16.9,14.5,16.7,22.3,12.6,16.5,28.3,6.2,16.6,19.3,13.3,14.9,19.7,19.5,11.3,18.7,16.5,17.6,16.4,14.6,18.5,16.8,15.2,16.9,18,16.8,15.6,15.1,19,14.7,18.8,15.2,18.3,16.7,16,15.4,17.2,15.5,18,17.4,15.5,16.2,17.4,16.5,29.3,7.1,13.1,17.7,14.9,17.1,17.7,16.6,17.4,19.6,18.7,11.7,16.5,17.1,14.8,16.4,22.6,17.3,19.3,9.3,16,17.9,15,16.1,19,13.9,23.9,10.8,17.5,15.9,16.8,15.7,16.5,16.2,17.6,17.1,27.3,6,15.1,19.6,14.4,19.3,13.4,16.8,17.1,17.6,17.8,15.4,16.6,17.6,14.3,16.9,21.5,14.8,13.5,16.8,18.9,17.1,14.9,18.2,17.1,15.5,17.6,17.3,15.4,21.3,10.1,21.7,11.3,20,15.4,25.8,7.1,17.2,14.8,18.3,17.1,16.1,18.5,15.7,16.8,20.1,13.3,16.5,15.8,17.6,16.6,16.6,14.1,19,15.3,18.8,15.7,20,12.9,16.9,17,16.8,16.3,15.5,18.3,17.6,14.1,16.9,16.7,21.2,14,31.4,5.6,11.4,16.5,17.6,16.3,15.3,18.3,16.8,16.4,18,15.9,16.1,16.6,15.1,17.9,16.7,17.2,17.2,14,18.6,16.9,16.6,17.3,16.3,15.6,17.7,16.4,14.7,17.6,15.6,18.3,15.3,18.6,26.9,8.7,14.3,15.9,18.2,15.7,16,17.6,20.5,12.3,15.8,17.8,17.2,14.4,18.6,16.3,17.1,16.3,16.7,17.9,15,17.6,17,15.4,15.3,21.6,12.1,18.4,17,15.6,16.8,20.1,13.8,17.4,16.1,72.5,9,8.8,9.6]
|
||||
@ -0,0 +1,103 @@
|
||||
{
|
||||
"pack": "phaser-catcher",
|
||||
"lane": "phaser",
|
||||
"game": "catcher",
|
||||
"url": "http://127.0.0.1:4312/host.html?game=catcher",
|
||||
"throttle": {
|
||||
"cpu_rate": 4
|
||||
},
|
||||
"method": "rAF 帧间隔采样器注入 iframe(只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 12s)",
|
||||
"sample_count": 633,
|
||||
"median_frame_interval_ms": 16.7,
|
||||
"median_fps": 59.88,
|
||||
"p99_frame_interval_ms": 28.3,
|
||||
"worst_1pct_fps": 35.34,
|
||||
"p95_frame_interval_ms": 20.5,
|
||||
"min_frame_interval_ms": 5.4,
|
||||
"max_frame_interval_ms": 72.5,
|
||||
"ended_during_sample": true,
|
||||
"samples_head": [
|
||||
16.2,
|
||||
17.3,
|
||||
17.5,
|
||||
16.2,
|
||||
17.4,
|
||||
15.1,
|
||||
17.6,
|
||||
16.5,
|
||||
15.7,
|
||||
17.3,
|
||||
16.2,
|
||||
17,
|
||||
16.9,
|
||||
15.9,
|
||||
17.1,
|
||||
15.5,
|
||||
19.5,
|
||||
14.6,
|
||||
17.6,
|
||||
14.6,
|
||||
16.8,
|
||||
27.5,
|
||||
6.6,
|
||||
16.8,
|
||||
18.4,
|
||||
16.3,
|
||||
17.5,
|
||||
15,
|
||||
16.1,
|
||||
18,
|
||||
16.1,
|
||||
20.4,
|
||||
13,
|
||||
19,
|
||||
15,
|
||||
14.2,
|
||||
21.9,
|
||||
11.2,
|
||||
17.2,
|
||||
18.1
|
||||
],
|
||||
"samples_tail": [
|
||||
18.6,
|
||||
26.9,
|
||||
8.7,
|
||||
14.3,
|
||||
15.9,
|
||||
18.2,
|
||||
15.7,
|
||||
16,
|
||||
17.6,
|
||||
20.5,
|
||||
12.3,
|
||||
15.8,
|
||||
17.8,
|
||||
17.2,
|
||||
14.4,
|
||||
18.6,
|
||||
16.3,
|
||||
17.1,
|
||||
16.3,
|
||||
16.7,
|
||||
17.9,
|
||||
15,
|
||||
17.6,
|
||||
17,
|
||||
15.4,
|
||||
15.3,
|
||||
21.6,
|
||||
12.1,
|
||||
18.4,
|
||||
17,
|
||||
15.6,
|
||||
16.8,
|
||||
20.1,
|
||||
13.8,
|
||||
17.4,
|
||||
16.1,
|
||||
72.5,
|
||||
9,
|
||||
8.8,
|
||||
9.6
|
||||
]
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
[19.2,18.2,12.2,20.3,14.7,17.1,12.8,21.5,13.4,19.8,13.5,16.9,16.5,19,16.9,15.6,18.9,16,12.9,21,15.9,17.4,16.2,16.7,13,16.7,15.6,16.7,18.4,16.2,15.5,19.6,14.6,16.4,17.6,17.3,18.8,12.7,16,18.5,15.5,16.6,17.2,18.1,14.7,16.4,17.6,16.2,16.9,17.3,17.2,16.1,15.4,17.1,17.1,16.5,16.3,18.1,16.1,15.8,18.4,14.9,17.6,15.7,17.5,16.2,16.6,18,16.3,14.9,17.3,16.3,19.9,13.4,16.9,16.2,18,18.2,13.6,17.7,17.5,14.6,18.5,15.6,19.8,16.2,14,16.2,19.2,16.6,14.9,19.4,15.5,18.5,14.7,19.5,16.7,16.8,11.4,18.3,16,17,20.1,15.3,13.4,19.4,17.4,16.4,16.5,16.9,16.6,17.5,15.6,16.1,16.7,17.8,13.8,17.8,20,11.3,21.1,13.5,17.4,16.8,14.6,20.1,16.7,18.1,11.8,16.8,16.9,19,14.2,16.7,16.2,19.1,16.8,16.8,19.3,16,12.2,17,18.4,15.6,20.4,13.6,19.8,15.8,15.4,17.3,16.5,17.6,16.2,16.9,17.7,14.2,15.4,16.7,16.3,18.1,14.5,17.9,16.1,18.4,16.1,16.1,19.1,15.1,16.1,17.9,16,17,17.1,19.4,14.3,16.8,15.4,16.7,17.4,16.3,15,18.9,16.5,17.5,14.3,18.7,14,16.6,17.4,17.7,14.3,19.2,16.5,15.5,18.2,13.8,17.7,17.7,19.1,12.3,19.3,17.8,14.3,20.8,16.9,14.7,15.8,16.6,19.2,12.2,19.3,14.8,15.6,16.3,17.7,17.7,15.1,17.7,15.5,19,14.9,19.5,15,16.7,16.5,18.4,14.2,17.6,16.5,16.7,17.5,14.4,19.6,17.2,15.9,16,14.7,19.4,16.5,15.8,17.5,14.8,15.3,16.2,21.2,13.4,18.9,15.8,14,18.6,16.6,15.6,17.4,18.5,17.3,16.3,16.7,15,15.8,18.9,15.6,15.8,16.2,18.9,17.5,15.8,16.8,14.3,20.8,12.4,16.9,16.5,18.4,16.4,15.4,16.1,16.4,18.4,15.9,19.3,14.5,18.1,15.5,20.1,13,17.5,15.2,19.4,14.9,17.7,19.1,14.2,15.1,18.2,17.4,15.1,19.3,14.3,14.6,18.8,18.5,14.8,18.3,13.8,18.2,14.8,19.3,16.6,15.2,22.2,11.1,16.8,14.6,16.7,17.6,17.1,15.4,17.7,19.9,13,17.9,17,16.1,18.8,12.6,18.5,21,14.8,15.8,16.7,14.3,19.7,17.4,14.8,19.1,13.6,18,20.8,15.8,17.4,13.6,18.1,13.6,18.4,16.1,15.8,14.1,19.2,16.3,13.8,20.8,16,19.9,15.1,15.4,16.3,15.1,18.5,14.2,17.3,15,18.9,14.2,19.3,17.8,19.6,10,18.2,15.2,16.8,16.7,16.4,18.9,14.6,16.8,16.6,16.9,16.4,17.7,18.4,20.9,10.7,18.4,14,17.1,17.8,16,16.1,16.8,17.3,18.8,18.8,13.7,17.9,14.3,18.1,14.8,15.8,19,14.4,20.6,16.1,15.1,18.7,15.4,14,19,14.2,17.1,19.5,17.7,17,11.8,19.5,16.6,16.7,14,19.7,14.9,17.8,18.7,15.4,16.1,16.3,16.8,17.6,14.5,18.3,16,16.2,16.7,18.6,18.5,12.7,15.1,17.2,16.4,21,15.2,17.3,16.4,18.4,12,17.6,16.5,16.9,15.9,17.5,16.2,19.2,14.5,15.1,18.1,17.5,17.6,17,15.6,19.7,14,17.4,15.9,19.2,12.5,19.6,17.2,16.5,14.3,16.8,16.1,18.9,15.4,15.3,19.3,13.9,17.9,15.4,17.6,16.7,16,18,16.7,15.5,18.9,14.5,17.5,19,15.4,15.9,15.5,19.2,16,19.6,11.1,16.3,16.2,19.4,15.3,18.2,16.5,15.3,20,16.1,18.3,16.7,15.2,15,17,15.5,19.2,14.1,16.9,16.4,16.2,18,17.2,16.8,17.4,15.8,17.8,12.6,17.4,21.8,12.3,16.8,17.4,19.4,12,17.3,19,17.3,15.9,15.6,16.1,16.8,16.4,16.5,20.2,14.8,19.1,14.7,16.7,19.1,14.4,15.4,18.5,12.1,19.3,16.4,18.3,15.1,17.3,15.6,15.7,18.8,14.6,20.7,14.3,14.8,18,17,16.7,17.9,16.8,17.9,17.3,14.4,18.1,13,19.1,17.5,13.2,19,14.4,16.1,20.5,14.5,16.6,18.3,16.5,14.5,18.8,16.3,17.5,20.3,9.3,19.8,19.1,12.1,18.2,17.2,14.7,16.4,18.8,17.9,15.7,17.7,16.2,15.9,13.9,16.9,18,14.4,17.7,18.2,15.4,16.6,16.1,16.6,20.4,13.8,18.2,13.7,18.4,15,18.7,15.5,17.7,16.6,16.4,15.8,20.6,15.7,13.4,17.5,18,15.3,16.4,19.7,15.5,17.3,15,17,20.9,13.1,15.7,18.6,13.3,18.1,18.6,17,15.2,18.3,18.7,13.6,17.3,16,17.6,12.5,17.9,17.3,18.5,16.2,17.9,17.3,15.5,17.7,13,18.2,18.4,14,19,17.4,14.6,17.9,15.1,17.1,20.6,19,11,16.7,17.9,15,18.3,18.8,10.8,17.1,15.8,17.1,18.2,16.3,18.1,16.8,17.1,15.6,16,15.9,18.4,17.5,15.8,15.9,18.7,16.2,16.3,16.9,17.1,14.2,16.9,15.3,18.4,16.8,17.1,16.4,17.8,14.5,16.2,17.8,18,13.9,17.5,18.2,14.4,17.8,16.7,14.8,19.3,17,16.3,16.6,16.3,17,19.2]
|
||||
@ -0,0 +1,103 @@
|
||||
{
|
||||
"pack": "phaser-shop",
|
||||
"lane": "phaser",
|
||||
"game": "shop",
|
||||
"url": "http://127.0.0.1:4312/host.html?game=shop",
|
||||
"throttle": {
|
||||
"cpu_rate": 4
|
||||
},
|
||||
"method": "rAF 帧间隔采样器注入 iframe(只读 perf.now() 帧间隔;真实 CDP 输入驱动至 juice 密集态后纯采样 12s)",
|
||||
"sample_count": 721,
|
||||
"median_frame_interval_ms": 16.7,
|
||||
"median_fps": 59.88,
|
||||
"p99_frame_interval_ms": 21,
|
||||
"worst_1pct_fps": 47.62,
|
||||
"p95_frame_interval_ms": 19.7,
|
||||
"min_frame_interval_ms": 9.3,
|
||||
"max_frame_interval_ms": 22.2,
|
||||
"ended_during_sample": false,
|
||||
"samples_head": [
|
||||
19.2,
|
||||
18.2,
|
||||
12.2,
|
||||
20.3,
|
||||
14.7,
|
||||
17.1,
|
||||
12.8,
|
||||
21.5,
|
||||
13.4,
|
||||
19.8,
|
||||
13.5,
|
||||
16.9,
|
||||
16.5,
|
||||
19,
|
||||
16.9,
|
||||
15.6,
|
||||
18.9,
|
||||
16,
|
||||
12.9,
|
||||
21,
|
||||
15.9,
|
||||
17.4,
|
||||
16.2,
|
||||
16.7,
|
||||
13,
|
||||
16.7,
|
||||
15.6,
|
||||
16.7,
|
||||
18.4,
|
||||
16.2,
|
||||
15.5,
|
||||
19.6,
|
||||
14.6,
|
||||
16.4,
|
||||
17.6,
|
||||
17.3,
|
||||
18.8,
|
||||
12.7,
|
||||
16,
|
||||
18.5
|
||||
],
|
||||
"samples_tail": [
|
||||
16.8,
|
||||
17.1,
|
||||
15.6,
|
||||
16,
|
||||
15.9,
|
||||
18.4,
|
||||
17.5,
|
||||
15.8,
|
||||
15.9,
|
||||
18.7,
|
||||
16.2,
|
||||
16.3,
|
||||
16.9,
|
||||
17.1,
|
||||
14.2,
|
||||
16.9,
|
||||
15.3,
|
||||
18.4,
|
||||
16.8,
|
||||
17.1,
|
||||
16.4,
|
||||
17.8,
|
||||
14.5,
|
||||
16.2,
|
||||
17.8,
|
||||
18,
|
||||
13.9,
|
||||
17.5,
|
||||
18.2,
|
||||
14.4,
|
||||
17.8,
|
||||
16.7,
|
||||
14.8,
|
||||
19.3,
|
||||
17,
|
||||
16.3,
|
||||
16.6,
|
||||
16.3,
|
||||
17,
|
||||
19.2
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
{
|
||||
"pack": "littlejs-catcher",
|
||||
"lane": "littlejs",
|
||||
"game": "catcher",
|
||||
"url": "http://127.0.0.1:4311/host.html?game=catcher",
|
||||
"note": "通关一局后采集;不节流(内存与节流无关)。口径差异:JSHeap=页面 JS 堆;RSS=全 chrome 进程合计(含渲染/GPU/IPC,远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。",
|
||||
"game_end": {
|
||||
"score": 30,
|
||||
"completed": true,
|
||||
"duration_ms": 27167,
|
||||
"outcome": "win"
|
||||
},
|
||||
"js_heap_used_bytes": 4096696,
|
||||
"js_heap_used_mb": 3.91,
|
||||
"js_heap_total_bytes": 11968512,
|
||||
"js_heap_total_mb": 11.41,
|
||||
"proc_rss_total_kb": 1044136,
|
||||
"proc_rss_total_mb": 1019.66,
|
||||
"documents": 2,
|
||||
"nodes": 82,
|
||||
"js_event_listeners": 17
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
{
|
||||
"pack": "littlejs-shop",
|
||||
"lane": "littlejs",
|
||||
"game": "shop",
|
||||
"url": "http://127.0.0.1:4311/host.html?game=shop",
|
||||
"note": "通关一局后采集;不节流(内存与节流无关)。口径差异:JSHeap=页面 JS 堆;RSS=全 chrome 进程合计(含渲染/GPU/IPC,远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。",
|
||||
"game_end": {
|
||||
"score": 100,
|
||||
"completed": true,
|
||||
"duration_ms": 45067,
|
||||
"outcome": "win"
|
||||
},
|
||||
"js_heap_used_bytes": 4170704,
|
||||
"js_heap_used_mb": 3.98,
|
||||
"js_heap_total_bytes": 6987776,
|
||||
"js_heap_total_mb": 6.66,
|
||||
"proc_rss_total_kb": 1041632,
|
||||
"proc_rss_total_mb": 1017.22,
|
||||
"documents": 2,
|
||||
"nodes": 84,
|
||||
"js_event_listeners": 17
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
{
|
||||
"pack": "phaser-catcher",
|
||||
"lane": "phaser",
|
||||
"game": "catcher",
|
||||
"url": "http://127.0.0.1:4312/host.html?game=catcher",
|
||||
"note": "通关一局后采集;不节流(内存与节流无关)。口径差异:JSHeap=页面 JS 堆;RSS=全 chrome 进程合计(含渲染/GPU/IPC,远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。",
|
||||
"game_end": {
|
||||
"score": 30,
|
||||
"completed": true,
|
||||
"duration_ms": 26542,
|
||||
"outcome": "win"
|
||||
},
|
||||
"js_heap_used_bytes": 6588732,
|
||||
"js_heap_used_mb": 6.28,
|
||||
"js_heap_total_bytes": 11272192,
|
||||
"js_heap_total_mb": 10.75,
|
||||
"proc_rss_total_kb": 1038164,
|
||||
"proc_rss_total_mb": 1013.83,
|
||||
"documents": 2,
|
||||
"nodes": 99,
|
||||
"js_event_listeners": 38
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
{
|
||||
"pack": "phaser-shop",
|
||||
"lane": "phaser",
|
||||
"game": "shop",
|
||||
"url": "http://127.0.0.1:4312/host.html?game=shop",
|
||||
"note": "通关一局后采集;不节流(内存与节流无关)。口径差异:JSHeap=页面 JS 堆;RSS=全 chrome 进程合计(含渲染/GPU/IPC,远大于 JS 堆,非同口径)。对照 B4≤150MB 应主看哪个口径在终裁注明。",
|
||||
"game_end": {
|
||||
"score": 100,
|
||||
"completed": true,
|
||||
"duration_ms": 43250,
|
||||
"outcome": "win"
|
||||
},
|
||||
"js_heap_used_bytes": 6370900,
|
||||
"js_heap_used_mb": 6.08,
|
||||
"js_heap_total_bytes": 8400896,
|
||||
"js_heap_total_mb": 8.01,
|
||||
"proc_rss_total_kb": 1038140,
|
||||
"proc_rss_total_mb": 1013.81,
|
||||
"documents": 2,
|
||||
"nodes": 102,
|
||||
"js_event_listeners": 38
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
{
|
||||
"pack": "littlejs-catcher",
|
||||
"lane": "littlejs",
|
||||
"game": "catcher",
|
||||
"url": "http://127.0.0.1:4311/host.html?game=catcher",
|
||||
"throttle": {
|
||||
"cpu_rate": 4,
|
||||
"download_mbps": 5,
|
||||
"upload_mbps": 1,
|
||||
"rtt_ms": 40
|
||||
},
|
||||
"reps_count": 3,
|
||||
"s2_by_open": {
|
||||
"cold": {
|
||||
"n": 3,
|
||||
"median_ms": 535.7000000000007,
|
||||
"min_ms": 521.7000000000007,
|
||||
"max_ms": 579.6000000000004,
|
||||
"raw": [
|
||||
579.6000000000004,
|
||||
521.7000000000007,
|
||||
535.7000000000007
|
||||
]
|
||||
},
|
||||
"open2": {
|
||||
"n": 3,
|
||||
"median_ms": 430.2,
|
||||
"min_ms": 410.20000000000005,
|
||||
"max_ms": 461.80000000000007,
|
||||
"raw": [
|
||||
461.80000000000007,
|
||||
430.2,
|
||||
410.20000000000005
|
||||
]
|
||||
},
|
||||
"open3": {
|
||||
"n": 3,
|
||||
"median_ms": 396.7,
|
||||
"min_ms": 387,
|
||||
"max_ms": 454.40000000000003,
|
||||
"raw": [
|
||||
387,
|
||||
454.40000000000003,
|
||||
396.7
|
||||
]
|
||||
}
|
||||
},
|
||||
"cache_benefit_ms_cold_minus_open3": 139,
|
||||
"detail": [
|
||||
{
|
||||
"rep": 1,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 579.6000000000004,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 15490.8,
|
||||
"t_runner_boot": 15994.6,
|
||||
"t_sdk_ready": 15995.2,
|
||||
"t_first_paint": 16072.2,
|
||||
"t_input_bound": 16070.4,
|
||||
"t_game_loaded": 16071.9,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 16116
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 461.80000000000007,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 107.4,
|
||||
"t_runner_boot": 486.9,
|
||||
"t_sdk_ready": 489.3,
|
||||
"t_first_paint": 578.7,
|
||||
"t_input_bound": 569.2,
|
||||
"t_game_loaded": 573.1,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 603
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 387,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 104,
|
||||
"t_runner_boot": 417,
|
||||
"t_sdk_ready": 419.9,
|
||||
"t_first_paint": 497.5,
|
||||
"t_input_bound": 491,
|
||||
"t_game_loaded": 491.5,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 527
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rep": 2,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 521.7000000000007,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 25079.2,
|
||||
"t_runner_boot": 25536,
|
||||
"t_sdk_ready": 25537.4,
|
||||
"t_first_paint": 25617.7,
|
||||
"t_input_bound": 25600.9,
|
||||
"t_game_loaded": 25601.8,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 25643
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 430.2,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 125.3,
|
||||
"t_runner_boot": 473.4,
|
||||
"t_sdk_ready": 474.7,
|
||||
"t_first_paint": 562.6,
|
||||
"t_input_bound": 555.5,
|
||||
"t_game_loaded": 559,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 593
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 454.40000000000003,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 108.3,
|
||||
"t_runner_boot": 481,
|
||||
"t_sdk_ready": 484,
|
||||
"t_first_paint": 568.4,
|
||||
"t_input_bound": 562.7,
|
||||
"t_game_loaded": 566.6,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 602
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rep": 3,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 535.7000000000007,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 25066.5,
|
||||
"t_runner_boot": 25523.2,
|
||||
"t_sdk_ready": 25525,
|
||||
"t_first_paint": 25610.7,
|
||||
"t_input_bound": 25602.2,
|
||||
"t_game_loaded": 25606.2,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 25642
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 410.20000000000005,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 108.5,
|
||||
"t_runner_boot": 424.5,
|
||||
"t_sdk_ready": 428.8,
|
||||
"t_first_paint": 525.4,
|
||||
"t_input_bound": 518.7,
|
||||
"t_game_loaded": 521.1,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 552
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 396.7,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 169.2,
|
||||
"t_runner_boot": 487.1,
|
||||
"t_sdk_ready": 489.4,
|
||||
"t_first_paint": 569.5,
|
||||
"t_input_bound": 565.9,
|
||||
"t_game_loaded": 567.9,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 604
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
{
|
||||
"pack": "littlejs-shop",
|
||||
"lane": "littlejs",
|
||||
"game": "shop",
|
||||
"url": "http://127.0.0.1:4311/host.html?game=shop",
|
||||
"throttle": {
|
||||
"cpu_rate": 4,
|
||||
"download_mbps": 5,
|
||||
"upload_mbps": 1,
|
||||
"rtt_ms": 40
|
||||
},
|
||||
"reps_count": 3,
|
||||
"s2_by_open": {
|
||||
"cold": {
|
||||
"n": 3,
|
||||
"median_ms": 592.4000000000015,
|
||||
"min_ms": 571.0999999999985,
|
||||
"max_ms": 666.9000000000001,
|
||||
"raw": [
|
||||
571.0999999999985,
|
||||
592.4000000000015,
|
||||
666.9000000000001
|
||||
]
|
||||
},
|
||||
"open2": {
|
||||
"n": 3,
|
||||
"median_ms": 484.00000000000006,
|
||||
"min_ms": 472.3,
|
||||
"max_ms": 489.5,
|
||||
"raw": [
|
||||
472.3,
|
||||
484.00000000000006,
|
||||
489.5
|
||||
]
|
||||
},
|
||||
"open3": {
|
||||
"n": 3,
|
||||
"median_ms": 460.30000000000007,
|
||||
"min_ms": 423,
|
||||
"max_ms": 522.9,
|
||||
"raw": [
|
||||
423,
|
||||
460.30000000000007,
|
||||
522.9
|
||||
]
|
||||
}
|
||||
},
|
||||
"cache_benefit_ms_cold_minus_open3": 132.1,
|
||||
"detail": [
|
||||
{
|
||||
"rep": 1,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 571.0999999999985,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 25071.9,
|
||||
"t_runner_boot": 25519.4,
|
||||
"t_sdk_ready": 25521.3,
|
||||
"t_first_paint": 25645.3,
|
||||
"t_input_bound": 25643,
|
||||
"t_game_loaded": 25645,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 25657
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 472.3,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 101.2,
|
||||
"t_runner_boot": 445.4,
|
||||
"t_sdk_ready": 451,
|
||||
"t_first_paint": 577.7,
|
||||
"t_input_bound": 573.5,
|
||||
"t_game_loaded": 575.4,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 586
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 423,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 166.4,
|
||||
"t_runner_boot": 465.8,
|
||||
"t_sdk_ready": 468.7,
|
||||
"t_first_paint": 594.4,
|
||||
"t_input_bound": 589.4,
|
||||
"t_game_loaded": 591.6,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 602
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rep": 2,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 592.4000000000015,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 25096.1,
|
||||
"t_runner_boot": 25564.9,
|
||||
"t_sdk_ready": 25566.6,
|
||||
"t_first_paint": 25690.6,
|
||||
"t_input_bound": 25688.5,
|
||||
"t_game_loaded": 25690.1,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 25701
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 484.00000000000006,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 118.7,
|
||||
"t_runner_boot": 480.2,
|
||||
"t_sdk_ready": 483.4,
|
||||
"t_first_paint": 607.1,
|
||||
"t_input_bound": 602.7,
|
||||
"t_game_loaded": 605.7,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 614
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 460.30000000000007,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 107.9,
|
||||
"t_runner_boot": 452.7,
|
||||
"t_sdk_ready": 455.4,
|
||||
"t_first_paint": 571.6,
|
||||
"t_input_bound": 568.2,
|
||||
"t_game_loaded": 570.2,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 580
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rep": 3,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 666.9000000000001,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 2488.5,
|
||||
"t_runner_boot": 2980.9,
|
||||
"t_sdk_ready": 2983.5,
|
||||
"t_first_paint": 3159.3,
|
||||
"t_input_bound": 3155.4,
|
||||
"t_game_loaded": 3155.8,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 3168
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 489.5,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 110.5,
|
||||
"t_runner_boot": 471.6,
|
||||
"t_sdk_ready": 473.9,
|
||||
"t_first_paint": 605.1,
|
||||
"t_input_bound": 600,
|
||||
"t_game_loaded": 602.7,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 614
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 522.9,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 112.7,
|
||||
"t_runner_boot": 496.4,
|
||||
"t_sdk_ready": 498.8,
|
||||
"t_first_paint": 641.1,
|
||||
"t_input_bound": 635.6,
|
||||
"t_game_loaded": 637,
|
||||
"t_game_start": null
|
||||
},
|
||||
"wall_ms": 649
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,198 @@
|
||||
{
|
||||
"pack": "phaser-catcher",
|
||||
"lane": "phaser",
|
||||
"game": "catcher",
|
||||
"url": "http://127.0.0.1:4312/host.html?game=catcher",
|
||||
"throttle": {
|
||||
"cpu_rate": 4,
|
||||
"download_mbps": 5,
|
||||
"upload_mbps": 1,
|
||||
"rtt_ms": 40
|
||||
},
|
||||
"reps_count": 3,
|
||||
"s2_by_open": {
|
||||
"cold": {
|
||||
"n": 3,
|
||||
"median_ms": 2858,
|
||||
"min_ms": 2792.3,
|
||||
"max_ms": 2870.6,
|
||||
"raw": [
|
||||
2870.6,
|
||||
2858,
|
||||
2792.3
|
||||
]
|
||||
},
|
||||
"open2": {
|
||||
"n": 3,
|
||||
"median_ms": 756.5,
|
||||
"min_ms": 723.4,
|
||||
"max_ms": 759.6,
|
||||
"raw": [
|
||||
723.4,
|
||||
759.6,
|
||||
756.5
|
||||
]
|
||||
},
|
||||
"open3": {
|
||||
"n": 3,
|
||||
"median_ms": 684.4,
|
||||
"min_ms": 645,
|
||||
"max_ms": 695.5,
|
||||
"raw": [
|
||||
684.4,
|
||||
695.5,
|
||||
645
|
||||
]
|
||||
}
|
||||
},
|
||||
"cache_benefit_ms_cold_minus_open3": 2173.6,
|
||||
"detail": [
|
||||
{
|
||||
"rep": 1,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 2870.6,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 25.2,
|
||||
"t_runner_boot": 2782.8,
|
||||
"t_sdk_ready": 2784.7,
|
||||
"t_input_bound": 2895.8,
|
||||
"t_game_loaded": 2896.7,
|
||||
"t_renderer_ready": 2897
|
||||
},
|
||||
"wall_ms": 27885
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 723.4,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 23.4,
|
||||
"t_runner_boot": 572.8,
|
||||
"t_sdk_ready": 573.8,
|
||||
"t_input_bound": 746.8,
|
||||
"t_game_loaded": 747.9,
|
||||
"t_renderer_ready": 750.4,
|
||||
"t_first_paint": 752.3
|
||||
},
|
||||
"wall_ms": 978
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 684.4,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 23.3,
|
||||
"t_runner_boot": 605.5,
|
||||
"t_sdk_ready": 607.2,
|
||||
"t_input_bound": 707.7,
|
||||
"t_game_loaded": 708.7,
|
||||
"t_renderer_ready": 710.2,
|
||||
"t_first_paint": 711.7
|
||||
},
|
||||
"wall_ms": 819
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rep": 2,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 2858,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 27.2,
|
||||
"t_runner_boot": 2767.8,
|
||||
"t_sdk_ready": 2770.1,
|
||||
"t_input_bound": 2885.2,
|
||||
"t_game_loaded": 2886.6,
|
||||
"t_renderer_ready": 2886.8,
|
||||
"t_first_paint": 2887.1
|
||||
},
|
||||
"wall_ms": 27907
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 759.6,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 21,
|
||||
"t_runner_boot": 602.7,
|
||||
"t_sdk_ready": 604.2,
|
||||
"t_input_bound": 780.6,
|
||||
"t_game_loaded": 781.1,
|
||||
"t_renderer_ready": 781.4,
|
||||
"t_first_paint": 784.6
|
||||
},
|
||||
"wall_ms": 1000
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 695.5,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 21.4,
|
||||
"t_runner_boot": 605.6,
|
||||
"t_sdk_ready": 607,
|
||||
"t_input_bound": 716.9,
|
||||
"t_game_loaded": 718.4,
|
||||
"t_renderer_ready": 718.7
|
||||
},
|
||||
"wall_ms": 843
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rep": 3,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 2792.3,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 24.2,
|
||||
"t_runner_boot": 2700.4,
|
||||
"t_sdk_ready": 2701.8,
|
||||
"t_input_bound": 2816.5,
|
||||
"t_game_loaded": 2817.9,
|
||||
"t_renderer_ready": 2819.3
|
||||
},
|
||||
"wall_ms": 27829
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 756.5,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 17.7,
|
||||
"t_runner_boot": 594.2,
|
||||
"t_sdk_ready": 594.6,
|
||||
"t_input_bound": 774.2,
|
||||
"t_game_loaded": 775.5,
|
||||
"t_renderer_ready": 776.9,
|
||||
"t_first_paint": 777.1
|
||||
},
|
||||
"wall_ms": 977
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 645,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 24.6,
|
||||
"t_runner_boot": 571.1,
|
||||
"t_sdk_ready": 572.5,
|
||||
"t_input_bound": 669.6,
|
||||
"t_game_loaded": 670.6,
|
||||
"t_renderer_ready": 672.4,
|
||||
"t_first_paint": 673.9
|
||||
},
|
||||
"wall_ms": 820
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,199 @@
|
||||
{
|
||||
"pack": "phaser-shop",
|
||||
"lane": "phaser",
|
||||
"game": "shop",
|
||||
"url": "http://127.0.0.1:4312/host.html?game=shop",
|
||||
"throttle": {
|
||||
"cpu_rate": 4,
|
||||
"download_mbps": 5,
|
||||
"upload_mbps": 1,
|
||||
"rtt_ms": 40
|
||||
},
|
||||
"reps_count": 3,
|
||||
"s2_by_open": {
|
||||
"cold": {
|
||||
"n": 3,
|
||||
"median_ms": 2817.7,
|
||||
"min_ms": 2812.6,
|
||||
"max_ms": 2883.1,
|
||||
"raw": [
|
||||
2883.1,
|
||||
2817.7,
|
||||
2812.6
|
||||
]
|
||||
},
|
||||
"open2": {
|
||||
"n": 3,
|
||||
"median_ms": 780.5,
|
||||
"min_ms": 765.9,
|
||||
"max_ms": 798.8,
|
||||
"raw": [
|
||||
765.9,
|
||||
780.5,
|
||||
798.8
|
||||
]
|
||||
},
|
||||
"open3": {
|
||||
"n": 3,
|
||||
"median_ms": 745.6,
|
||||
"min_ms": 716.3,
|
||||
"max_ms": 752.8,
|
||||
"raw": [
|
||||
716.3,
|
||||
752.8,
|
||||
745.6
|
||||
]
|
||||
}
|
||||
},
|
||||
"cache_benefit_ms_cold_minus_open3": 2072.1,
|
||||
"detail": [
|
||||
{
|
||||
"rep": 1,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 2883.1,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 23,
|
||||
"t_runner_boot": 2764,
|
||||
"t_sdk_ready": 2765,
|
||||
"t_input_bound": 2906.1,
|
||||
"t_game_loaded": 2906.5,
|
||||
"t_renderer_ready": 2908.9,
|
||||
"t_first_paint": 2909.1
|
||||
},
|
||||
"wall_ms": 25752
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 765.9,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 20.2,
|
||||
"t_runner_boot": 579.5,
|
||||
"t_sdk_ready": 581.1,
|
||||
"t_input_bound": 786.1,
|
||||
"t_game_loaded": 786.4,
|
||||
"t_renderer_ready": 786.7,
|
||||
"t_first_paint": 789.6
|
||||
},
|
||||
"wall_ms": 1014
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 716.3,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 23.6,
|
||||
"t_runner_boot": 603.5,
|
||||
"t_sdk_ready": 605.3,
|
||||
"t_input_bound": 739.9,
|
||||
"t_game_loaded": 740.1,
|
||||
"t_renderer_ready": 740.3,
|
||||
"t_first_paint": 743.9
|
||||
},
|
||||
"wall_ms": 969
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rep": 2,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 2817.7,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 25.5,
|
||||
"t_runner_boot": 2704.6,
|
||||
"t_sdk_ready": 2705.9,
|
||||
"t_input_bound": 2843.2,
|
||||
"t_game_loaded": 2844.9,
|
||||
"t_renderer_ready": 2845.6,
|
||||
"t_first_paint": 2847
|
||||
},
|
||||
"wall_ms": 27934
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 780.5,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 24.7,
|
||||
"t_runner_boot": 594.9,
|
||||
"t_sdk_ready": 596,
|
||||
"t_input_bound": 805.2,
|
||||
"t_game_loaded": 806,
|
||||
"t_renderer_ready": 807.4,
|
||||
"t_first_paint": 808.3
|
||||
},
|
||||
"wall_ms": 1046
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 752.8,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 25.9,
|
||||
"t_runner_boot": 646.5,
|
||||
"t_sdk_ready": 647.7,
|
||||
"t_input_bound": 778.7,
|
||||
"t_game_loaded": 780.1,
|
||||
"t_renderer_ready": 781
|
||||
},
|
||||
"wall_ms": 910
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"rep": 3,
|
||||
"opens": [
|
||||
{
|
||||
"open": "cold",
|
||||
"s2_ms": 2812.6,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 26.3,
|
||||
"t_runner_boot": 2700.8,
|
||||
"t_sdk_ready": 2701.8,
|
||||
"t_input_bound": 2838.9,
|
||||
"t_game_loaded": 2840.2,
|
||||
"t_renderer_ready": 2840.4,
|
||||
"t_first_paint": 2841.9
|
||||
},
|
||||
"wall_ms": 27945
|
||||
},
|
||||
{
|
||||
"open": "open2",
|
||||
"s2_ms": 798.8,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 14.7,
|
||||
"t_runner_boot": 600.3,
|
||||
"t_sdk_ready": 602,
|
||||
"t_input_bound": 813.5,
|
||||
"t_game_loaded": 813.8,
|
||||
"t_renderer_ready": 814.1,
|
||||
"t_first_paint": 817.6
|
||||
},
|
||||
"wall_ms": 1026
|
||||
},
|
||||
{
|
||||
"open": "open3",
|
||||
"s2_ms": 745.6,
|
||||
"order_ok": true,
|
||||
"anchors": {
|
||||
"t_nav": 28,
|
||||
"t_runner_boot": 637.9,
|
||||
"t_sdk_ready": 639.7,
|
||||
"t_input_bound": 773.6,
|
||||
"t_game_loaded": 773.9,
|
||||
"t_renderer_ready": 774
|
||||
},
|
||||
"wall_ms": 912
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
docs/agent-specs/2026-06-11-tier1-engine-spike/phase2/run_fps_mem_all.sh
Executable file
17
docs/agent-specs/2026-06-11-tier1-engine-spike/phase2/run_fps_mem_all.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 2 · B+C:帧率@4× + 内存 —— 4 包顺序跑(避免 chrome 争用)
|
||||
set -u
|
||||
cd /root/spike-t1/phase2
|
||||
echo "=== FPS+MEM 全矩阵开跑 @ $(date -Is) ==="
|
||||
for pack in "littlejs catcher" "littlejs shop" "phaser catcher" "phaser shop"; do
|
||||
set -- $pack
|
||||
echo "--- [FPS] $1/$2 @ $(date -Is) ---"
|
||||
node fps_measure.cjs "$1" "$2" 12
|
||||
echo "--- [FPS] $1/$2 完 rc=$? ---"
|
||||
sleep 2
|
||||
echo "--- [MEM] $1/$2 @ $(date -Is) ---"
|
||||
node mem_measure.cjs "$1" "$2"
|
||||
echo "--- [MEM] $1/$2 完 rc=$? ---"
|
||||
sleep 2
|
||||
done
|
||||
echo "=== FPS+MEM 全矩阵完 @ $(date -Is) ==="
|
||||
14
docs/agent-specs/2026-06-11-tier1-engine-spike/phase2/run_s2_all.sh
Executable file
14
docs/agent-specs/2026-06-11-tier1-engine-spike/phase2/run_s2_all.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 2 · A. S2 三开曲线 —— 4 包 × 3 遍,顺序跑(避免 chrome 资源争用/端口碰撞)
|
||||
set -u
|
||||
cd /root/spike-t1/phase2
|
||||
REPS="${1:-3}"
|
||||
echo "=== S2 全矩阵开跑 reps=$REPS @ $(date -Is) ==="
|
||||
for pack in "littlejs catcher" "littlejs shop" "phaser catcher" "phaser shop"; do
|
||||
set -- $pack
|
||||
echo "--- 包 $1/$2 @ $(date -Is) ---"
|
||||
node s2_three_open.cjs "$1" "$2" "$REPS"
|
||||
echo "--- 包 $1/$2 完 rc=$? @ $(date -Is) ---"
|
||||
sleep 2
|
||||
done
|
||||
echo "=== S2 全矩阵完 @ $(date -Is) ==="
|
||||
@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Phase 2 · A. S2 三开生死曲线(钉子⑤主数据)—— 执行单 §6.1/§6.2
|
||||
* ============================================================================
|
||||
* 对每包(lane × game = 4 包)在 CDP 节流(CPU 4× + 网络 5Mbps/1Mbps/RTT40ms)下,
|
||||
* 同 profile 同 URL 做 冷开/二开/三开,各记完整七锚点 + S2(t_nav→t_input_bound)。
|
||||
* - 冷开 = 全新 chrome 用户数据目录(HTTP 缓存 + V8 编译缓存 双冷)→ 导航 → 收七锚点;
|
||||
* - 二开/三开 = 同一 chrome 同 profile,关页重开(非刷新)同 URL;
|
||||
* - 编译缓存收益 = 三开 vs 冷开的 S2 差值(Chromium 代理;真机 XWeb 留残差)。
|
||||
*
|
||||
* 每包跑 N=3 遍(每遍=一个全新 chrome+全新 user-data-dir 的「冷→二→三」三连开)取中位 + 离散。
|
||||
* host 自带单时钟 s2_ms:littlejs 读 window.__probe.summary().s2_ms / phaser 读 window.__SPIKE__.checks.s2_ms。
|
||||
* 锚点:littlejs window.__probe.anchors()(绝对 perf.now);phaser window.__SPIKE__.lifecycle(相对 T0)。
|
||||
*
|
||||
* 红线:只读 host 探针,不篡改任何状态、不注入事件。仅测「导航→可玩」生死曲线,不玩游戏。
|
||||
*
|
||||
* 用法:node s2_three_open.cjs <lane> <game> [reps]
|
||||
* lane=littlejs|phaser game=catcher|shop reps 缺省 3
|
||||
*/
|
||||
'use strict';
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
|
||||
// ---- 参数 ----
|
||||
const LANE = process.argv[2]; // littlejs | phaser
|
||||
const GAME = process.argv[3]; // catcher | shop
|
||||
const REPS = parseInt(process.argv[4] || '3', 10);
|
||||
if (!['littlejs', 'phaser'].includes(LANE) || !['catcher', 'shop'].includes(GAME)) {
|
||||
console.error('用法: node s2_three_open.cjs <littlejs|phaser> <catcher|shop> [reps]');
|
||||
process.exit(64);
|
||||
}
|
||||
const PORT = LANE === 'littlejs' ? 4311 : 4312;
|
||||
// 两 lane 同走测量宿主 host.html?game=(serve 把 / 与 /host.html 都映射到 host.html;带七锚点探针)
|
||||
const HOST_URL = 'http://127.0.0.1:' + PORT + '/host.html?game=' + GAME;
|
||||
const CDP_PORT = 9300 + (LANE === 'littlejs' ? 0 : 1) * 2 + (GAME === 'shop' ? 1 : 0); // 每包独立调试端口
|
||||
|
||||
const OUTDIR = path.join(__dirname, 'raw');
|
||||
if (!fs.existsSync(OUTDIR)) fs.mkdirSync(OUTDIR, { recursive: true });
|
||||
const TAG = LANE + '-' + GAME;
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const logLines = [];
|
||||
function log(...a) {
|
||||
const s = '[s2:' + TAG + '] ' + a.map((x) => (typeof x === 'string' ? x : JSON.stringify(x))).join(' ');
|
||||
console.log(s);
|
||||
logLines.push(s);
|
||||
}
|
||||
|
||||
// ---- 极简 CDP over WebSocket(node22 内置 WebSocket,零三方依赖)----
|
||||
class CDP {
|
||||
constructor(wsUrl) {
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
this.id = 0;
|
||||
this.pending = new Map();
|
||||
this.handlers = {};
|
||||
this.ready = new Promise((res, rej) => {
|
||||
this.ws.addEventListener('open', () => res());
|
||||
this.ws.addEventListener('error', (e) => rej(e));
|
||||
});
|
||||
this.ws.addEventListener('message', (ev) => {
|
||||
let msg; try { msg = JSON.parse(ev.data); } catch { return; }
|
||||
if (msg.id !== undefined && this.pending.has(msg.id)) {
|
||||
const { resolve, reject } = this.pending.get(msg.id);
|
||||
this.pending.delete(msg.id);
|
||||
if (msg.error) reject(new Error(JSON.stringify(msg.error)));
|
||||
else resolve(msg.result);
|
||||
} else if (msg.method) {
|
||||
(this.handlers[msg.method] || []).forEach((h) => h(msg.params));
|
||||
}
|
||||
});
|
||||
}
|
||||
on(m, h) { (this.handlers[m] ||= []).push(h); }
|
||||
send(method, params) {
|
||||
const id = ++this.id;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.set(id, { resolve, reject });
|
||||
this.ws.send(JSON.stringify({ id, method, params: params || {} }));
|
||||
});
|
||||
}
|
||||
close() { try { this.ws.close(); } catch {} }
|
||||
}
|
||||
|
||||
function httpJson(urlPath, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get('http://127.0.0.1:' + port + urlPath, (res) => {
|
||||
let b = ''; res.on('data', (d) => (b += d));
|
||||
res.on('end', () => { try { resolve(JSON.parse(b)); } catch (e) { reject(e); } });
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- 启 chrome(全新 user-data-dir = HTTP 缓存 + V8 编译缓存双冷)----
|
||||
function launchChrome(userDataDir) {
|
||||
const args = [
|
||||
'--headless=new',
|
||||
'--no-sandbox',
|
||||
'--disable-gpu',
|
||||
'--mute-audio',
|
||||
'--hide-scrollbars',
|
||||
'--window-size=900,900',
|
||||
'--force-device-scale-factor=2',
|
||||
'--remote-debugging-port=' + CDP_PORT,
|
||||
'--remote-debugging-address=127.0.0.1',
|
||||
'--user-data-dir=' + userDataDir,
|
||||
'about:blank',
|
||||
];
|
||||
const ch = cp.spawn('/usr/bin/google-chrome', args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
ch.stderr.on('data', () => {});
|
||||
return ch;
|
||||
}
|
||||
|
||||
// ---- 在某 page target 上:节流 + 导航 + 等 input_bound + 读锚点 ----
|
||||
async function openAndMeasure(bcdp, openLabel) {
|
||||
// 新建 page target(关页重开 = 新 target;非刷新)
|
||||
const { targetId } = await bcdp.send('Target.createTarget', { url: 'about:blank' });
|
||||
let pageInfo = null;
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const list = await httpJson('/json', CDP_PORT);
|
||||
pageInfo = list.find((t) => t.id === targetId && t.webSocketDebuggerUrl);
|
||||
if (pageInfo) break;
|
||||
await sleep(120);
|
||||
}
|
||||
if (!pageInfo) throw new Error(openLabel + ': page target ws 未找到');
|
||||
const cdp = new CDP(pageInfo.webSocketDebuggerUrl);
|
||||
await cdp.ready;
|
||||
|
||||
await cdp.send('Page.enable');
|
||||
await cdp.send('Runtime.enable');
|
||||
await cdp.send('Network.enable');
|
||||
// —— 节流(执行单 §6.2:千元机+4G 画像)——
|
||||
// CPU 4×
|
||||
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 4 });
|
||||
// 网络:下行 5Mbps / 上行 1Mbps / RTT 40ms(字节/秒口径:Mbps→bytes/s = Mbps*1e6/8)
|
||||
await cdp.send('Network.emulateNetworkConditions', {
|
||||
offline: false,
|
||||
latency: 40,
|
||||
downloadThroughput: (5 * 1e6) / 8,
|
||||
uploadThroughput: (1 * 1e6) / 8,
|
||||
});
|
||||
|
||||
// 导航前墙钟(仅作旁证;S2 以 host 单时钟为准)
|
||||
const wallStart = Date.now();
|
||||
await cdp.send('Page.navigate', { url: HOST_URL });
|
||||
|
||||
// 轮询 host 探针拿 s2_ms(host 内部已算 t_input_bound - t_nav)
|
||||
let anchors = null, s2 = null, orderOk = null, loaded = null;
|
||||
const readProbe = LANE === 'littlejs'
|
||||
? `(function(){ if(!window.__probe) return null; return { s2: window.__probe.summary().s2_ms, anchors: window.__probe.anchors(), order: window.__probe.summary().loaded_ge_input_bound }; })()`
|
||||
: `(function(){ var s=window.__SPIKE__; if(!s) return null; return { s2: s.checks ? s.checks.s2_ms : null, anchors: s.lifecycle, order: s.checks ? s.checks.order_ok : null }; })()`;
|
||||
for (let i = 0; i < 300; i++) { // 节流下 input_bound 可能较慢,给足 30s
|
||||
const r = await cdp.send('Runtime.evaluate', { expression: 'JSON.stringify(' + readProbe + ')', returnByValue: true })
|
||||
.then((x) => { try { return JSON.parse(x.result.value); } catch { return null; } })
|
||||
.catch(() => null);
|
||||
if (r && r.s2 != null) { s2 = r.s2; anchors = r.anchors; orderOk = r.order; break; }
|
||||
await sleep(100);
|
||||
}
|
||||
const wallMs = Date.now() - wallStart;
|
||||
|
||||
// 关页(下一开是新 target;profile 内磁盘缓存+V8 code cache 保留)
|
||||
await cdp.send('Page.close').catch(() => {});
|
||||
cdp.close();
|
||||
await bcdp.send('Target.closeTarget', { targetId }).catch(() => {});
|
||||
|
||||
return { open: openLabel, s2_ms: s2, order_ok: orderOk, anchors, wall_ms: wallMs };
|
||||
}
|
||||
|
||||
// ---- 一遍 = 一个全新 chrome+全新 profile 的「冷→二→三」三连开 ----
|
||||
async function oneRep(rep) {
|
||||
const userDataDir = '/tmp/s2-' + TAG + '-rep' + rep + '-' + Date.now();
|
||||
fs.rmSync(userDataDir, { recursive: true, force: true });
|
||||
log('rep#' + rep + ' 启 chrome(全新 profile ' + userDataDir + ')');
|
||||
const chrome = launchChrome(userDataDir);
|
||||
|
||||
// 等调试端口
|
||||
let ver = null;
|
||||
for (let i = 0; i < 50; i++) {
|
||||
try { ver = await httpJson('/json/version', CDP_PORT); if (ver && ver.webSocketDebuggerUrl) break; } catch {}
|
||||
await sleep(250);
|
||||
}
|
||||
if (!ver) { chrome.kill('SIGKILL'); throw new Error('rep#' + rep + ' chrome 调试端口未就绪'); }
|
||||
const bcdp = new CDP(ver.webSocketDebuggerUrl);
|
||||
await bcdp.ready;
|
||||
|
||||
const opens = [];
|
||||
for (const label of ['cold', 'open2', 'open3']) {
|
||||
const r = await openAndMeasure(bcdp, label);
|
||||
log('rep#' + rep + ' ' + label + ' S2=' + r.s2_ms + 'ms order_ok=' + r.order_ok + ' wall=' + r.wall_ms + 'ms');
|
||||
opens.push(r);
|
||||
await sleep(400); // 让 V8 code cache 落盘
|
||||
}
|
||||
|
||||
bcdp.close();
|
||||
chrome.kill('SIGKILL');
|
||||
await sleep(400);
|
||||
fs.rmSync(userDataDir, { recursive: true, force: true });
|
||||
return { rep, opens };
|
||||
}
|
||||
|
||||
function median(arr) {
|
||||
const a = arr.filter((x) => x != null).slice().sort((x, y) => x - y);
|
||||
if (!a.length) return null;
|
||||
const m = Math.floor(a.length / 2);
|
||||
return a.length % 2 ? a[m] : (a[m - 1] + a[m]) / 2;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
log('=== S2 三开生死曲线 · ' + TAG + ' · URL=' + HOST_URL + ' · 节流 CPU4×+5Mbps/1Mbps/RTT40 · reps=' + REPS + ' ===');
|
||||
const reps = [];
|
||||
for (let r = 1; r <= REPS; r++) {
|
||||
try { reps.push(await oneRep(r)); }
|
||||
catch (e) { log('rep#' + r + ' 异常: ' + (e && e.message)); reps.push({ rep: r, error: e && e.message, opens: [] }); }
|
||||
}
|
||||
|
||||
// 汇总:每开位 S2 的中位+min/max(离散)
|
||||
const byOpen = { cold: [], open2: [], open3: [] };
|
||||
for (const rep of reps) for (const o of (rep.opens || [])) if (byOpen[o.open]) byOpen[o.open].push(o.s2_ms);
|
||||
const summ = {};
|
||||
for (const k of ['cold', 'open2', 'open3']) {
|
||||
const vals = byOpen[k];
|
||||
summ[k] = {
|
||||
n: vals.filter((x) => x != null).length,
|
||||
median_ms: median(vals),
|
||||
min_ms: vals.length ? Math.min(...vals.filter((x) => x != null)) : null,
|
||||
max_ms: vals.length ? Math.max(...vals.filter((x) => x != null)) : null,
|
||||
raw: vals,
|
||||
};
|
||||
}
|
||||
const coldMed = summ.cold.median_ms, hotMed = summ.open3.median_ms;
|
||||
const cacheBenefit = (coldMed != null && hotMed != null) ? +(coldMed - hotMed).toFixed(2) : null;
|
||||
|
||||
const out = {
|
||||
pack: TAG, lane: LANE, game: GAME, url: HOST_URL,
|
||||
throttle: { cpu_rate: 4, download_mbps: 5, upload_mbps: 1, rtt_ms: 40 },
|
||||
reps_count: REPS,
|
||||
s2_by_open: summ,
|
||||
cache_benefit_ms_cold_minus_open3: cacheBenefit,
|
||||
detail: reps,
|
||||
};
|
||||
fs.writeFileSync(path.join(OUTDIR, 's2-' + TAG + '.json'), JSON.stringify(out, null, 2));
|
||||
fs.writeFileSync(path.join(OUTDIR, 's2-' + TAG + '.log'), logLines.join('\n') + '\n');
|
||||
log('DONE cold med=' + coldMed + ' open2 med=' + summ.open2.median_ms + ' open3 med=' + hotMed + ' 编译缓存收益(cold-open3)=' + cacheBenefit + 'ms');
|
||||
process.exit(0);
|
||||
})().catch((e) => { console.error(e); process.exit(1); });
|
||||
Loading…
x
Reference in New Issue
Block a user