INP 自动化归因与自愈:如何自动定位、修复并验证一次卡顿
传统排查 vs 自动化
INP 超标了(一次交互卡了 350ms)。传统排查:人打开 DevTools,人眼翻 Event Timing 拆三段,人脑猜"可能是这个脚本",人手写修复,人刷新看"好像快了"。慢、靠猜、不可复现。
自动化归因与自愈要做的,是自动完成诊断→修复→验证→防复发 ,最终给出一个结构化结论。下面用一个案例走完全流程:点击购买按钮卡了 350ms,自动诊断出 vendor.js 布局抖动、自动修复、自动验证有效。
自动归因:自动拆解延迟、自动定位元凶
第一步:自动判定卡在哪一段
每次交互的 Event Timing 记录已把延迟拆成三段(inputDelay 排队、processingDuration 处理、presentationDelay 呈现)。归因的第一步,是自动比较三段,判定哪段主导:
js
// 自动判定:比较三段,找出主导段(规则,不靠猜)
function autoAttribute(entry) {
const phases = {
inputDelay: entry.inputDelay, // 150ms
processingDuration: entry.processingDuration, // 120ms
presentationDelay: entry.duration - entry.inputDelay - entry.processingDuration, // 80ms
};
const dominant = Object.entries(phases).sort((a, b) => b[1] - a[1])[0];
return { dominant: dominant[0], phases }; // 自动判定:inputDelay(150ms) 主导
}
判定结果:inputDelay(150ms)主导------主线程被别的代码占着,不是按钮代码的问题。 这一步是规则判定(比较大小),不靠人翻、不靠 AI 猜。
第二步:自动定位占用主线程的脚本
inputDelay 主导,说明点击发生时主线程正忙。忙什么? 自动查 LoAF(Long Animation Frames)------它记录主线程每帧超过 50ms 的工作。按这次交互的时间窗口,自动匹配 LoAF 条目,把帧里的 scripts[] 按耗时排序,定位占用最大的脚本:
js
// 自动匹配 LoAF,按耗时排序,定位责任脚本(规则:时间窗口 + 排序)
function autoFindCulprit(interactionTime) {
return performance.getEntriesByType('long-animation-frame')
.filter(f => Math.abs(f.startTime - interactionTime) < 500) // 自动按时间窗口匹配
.flatMap(f => f.scripts || [])
.sort((a, b) => b.duration - a.duration)[0]; // 自动按耗时排序取最大
// 自动定位:vendor.js,duration 280ms,其中强制布局 180ms
}
自动定位到:vendor.js 占了主线程 280ms,其中 180ms 是强制布局(布局抖动)。 再用 sourcemap 自动把压缩 URL 反解回源码具体哪行。全程规则判定------时间窗口匹配、耗时排序、sourcemap 反解,不需人翻 DevTools。
自动修复:自动选方案、自动注入
自动选择修复方案
拿到结构化根因(inputDelay 主导 + vendor.js 布局抖动)后,自动映射到对应的修复方案。映射是预定义的规则表:
| 根因 | 自动选择的修复 |
|---|---|
| inputDelay(主线程被占用) | scheduler.yield() 让步------切断长任务,让输入插队 |
| processingDuration(handler 慢) | 拆分长 handler / 移入 Web Worker |
| presentationDelay(渲染开销) | 减少布局抖动 / 虚拟列表 |
判定为 inputDelay + 布局抖动 → 自动选择 scheduler.yield() 让步方案。
自动注入修复
选定后,自动把修复注入页面(chrome.scripting 运行时注入),不需人改代码:
js
// 自动注入:拦截 vendor.js 长循环,分块 + yield 让步
chrome.scripting.executeScript({
target: { tabId }, world: 'MAIN', injectImmediately: true,
func: () => {
const origProcess = window.vendorProcess;
window.vendorProcess = async function(items) {
for (const item of items) {
origProcess.call(this, item);
if (navigator.scheduling?.isInputPending?.()) {
await scheduler.yield(); // 让出主线程,排队的点击得以处理
}
}
};
}
});
注入后记录回滚标记------验证不通过则自动还原。
自动验证:自动重放、自动统计判定
自动重放并采样
修复后,自动用预先录制的交互序列重放,修复前后各跑 N 次,自动采集每次 inputDelay:
js
// 自动验证:重放交互序列,修前 vs 修后各 N 次,自动采集
async function autoVerify(replayFn, fixFn) {
const baseline = [];
for (let i = 0; i < 6; i++) baseline.push(await replayFn()); // 自动采基线
await fixFn(); // 自动应用修复
const treated = [];
for (let i = 0; i < 6; i++) treated.push(await replayFn()); // 自动采修复后
return autoPairedTTest(baseline, treated); // 自动统计判定
}
自动统计判定
自动运行配对 t 检验,判定修复是否统计显著有效,并做方向校验与诚实降级:
js
// 自动判定:配对 t + 方向校验 + 诚实降级(规则,不靠人看)
function autoPairedTTest(baseline, treated) {
const n = Math.min(baseline.length, treated.length);
if (n < 4) return { verdict: 'inconclusive', reason: '样本不足,不下结论' };
const diffs = [];
for (let i = 0; i < n; i++) diffs.push(treated[i] - baseline[i]);
const mean = diffs.reduce((s, d) => s + d, 0) / n;
const variance = diffs.reduce((s, d) => s + (d - mean) ** 2, 0) / (n - 1);
const t = mean / Math.sqrt(variance / n);
const significant = twoSidedP(t, n - 1) < 0.05;
if (significant && mean < 0) return { verdict: 'proven', drop: -mean }; // 显著 + 方向降
if (significant && mean >= 0) return { verdict: 'failed', reason: '方向反弹,自动回滚修复' };
return { verdict: 'inconclusive', reason: '差异不显著,不下结论' };
}
判定结果:inputDelay 从均值 152ms 降到 20ms,p < 0.05,方向正确 → 自动判定 proven。 方向反弹则自动回滚;差异不显著则诚实标 inconclusive,不硬编"已修复"。不靠人刷新看"感觉好了"。
自动沉淀:回归防复发
验证通过后,自动把"交互序列 + 根因 + 修复 + 验证结果"存成回归用例。后续页面有任何改动,自动重放这段交互并采 inputDelay------一旦回升,自动报警。不需人记得"以前修过这个"。
全程自动,人只看结论
| 环节 | 传统(人做) | 自动化 |
|---|---|---|
| 拆解延迟 | 人翻 Event Timing | 自动比较三段,判定主导 |
| 找元凶 | 人猜"可能这个脚本" | 自动匹配 LoAF,按耗时排序定位 |
| 选修复 | 人想方案、人改代码 | 自动映射根因→修复,自动注入 |
| 验证 | 人刷新看"好像快了" | 自动重放 6 次,配对 t 判定 |
| 防复发 | 没有 | 自动存回归,自动报警 |
最终产出的结论:"inputDelay 主导,vendor.js 布局抖动(280ms),已自动 yield 修复,inputDelay 从 152ms 统计显著降至 20ms(p<0.05),回归已存。" 人不参与诊断、修复、验证------只看结论、确认授权。
这套自动化的根基是确定性规则 (三段比较、时间窗口匹配、根因→修复映射、统计检验)。根因可枚举、特征可匹配、修复有模板、验证有统计------这些前提让自动化成为可能,不依赖 AI 猜测。同样的架构适用于 CLS 的跳动、内存的泄漏、运行时错误:自动归因→自动自愈→自动沉淀。INP 只是其中一个。
依据
- Event Timing API:w3c.github.io/event-timin...
- Long Animation Frames API:w3c.github.io/long-animat...
- scheduler.yield:developer.mozilla.org/docs/Web/AP...
- web.dev INP:web.dev/articles/in...