为什么需要一份口径速查
同一个词,在不同团队嘴里经常不是同一件事。产品说“首屏慢”,指的是打开页面到看见内容;工程师说“首屏慢”,指的是 LCP 分位变差;服务端说“不慢”,指的是接口 P95 只有几十毫秒。三方都没说谎,但讨论无法收敛,因为指标的起点、终点和分母不同。
这份附录不追求覆盖所有指标,只固定卷一反复使用的那一组:加载类的 FP、FCP、LCP,交互类的 INP、TBT,视觉稳定性的 CLS,以及端上特有的启动、崩溃、卡顿与内存指标。每个指标都写清楚四件事:测量什么、从哪里开始、到哪里结束、分母是谁。口径一旦写死,跨团队讨论才有共同语言。
文中出现的阈值、比例和耗时,除非明确标注为本机输出,都是案例假设值,用来演示判断方法,不是任何真实产品的实测结论。
指标的定义与测量边界
FP 与 FCP
FP(First Paint)是浏览器第一次把任何像素画到屏幕上的时刻,包括背景色。它回答“页面有没有开始响应”,但不回答“用户有没有看到内容”。一个只有白色背景的页面也可以有 FP。
FCP(First Contentful Paint)是第一次绘制文本、图像、Canvas 或 SVG 等内容的时刻。它比 FP 更接近用户感知,但仍然只代表“有东西出现”,不代表主要内容出现,也不代表可以交互。
两个指标的边界都在“首次绘制”,因此容易受字体、首屏骨架和占位图影响。一个立即渲染骨架屏的页面 FCP 很好,用户却可能仍要等很久才看到真实内容。用 FCP 单独做考核,会诱导团队把骨架屏做得更早、更漂亮,而不是让真实内容更早到达。
LCP
LCP(Largest Contentful Paint)是视口内最大内容元素的绘制时刻。它通常对应首屏主图、大标题或文章正文块,因此比 FCP 更接近“主要内容可见”。
LCP 有三个必须写进口径的细节:
- 候选元素会随页面加载变化。先出现的大标题可能被后加载的主图取代,最终 LCP 取的是最后一个候选的绘制时间。
- 用户在页面上的首次交互会终止 LCP 的候选更新。也就是说,用户等不及点了一下,后面的内容再大也不再计入 LCP。
- 页面进入后台或不可见时,LCP 停止记录。单页应用切换路由不会自动重置 LCP,需要按路由重新采集。
最后一个细节最容易被忽略:在一个长会话里只取一个 LCP,会把第二个页面的首屏算成“很快”,因为计时起点还是应用启动。按路由采集时,起点应是路由切换开始渲染的时刻,终点是该路由主要内容绘制完成的时刻。
INP
INP(Interaction to Next Paint)衡量用户交互到下一帧绘制的延迟。它观察页面生命周期内的多次交互,取一个高分位作为代表值,而不是只看第一次输入。与它容易混淆的是已经退场的 FID:FID 只测第一次输入的响应延迟,不关心后续绘制,也不关心慢交互。
INP 的口径难点在“交互”的边界。键盘输入、点击、触摸都算;滚动和悬停通常不算独立交互,但会参与帧的绘制。若一个点击触发了网络请求,INP 只等到下一帧绘制,不等请求完成。把“点击到数据回来”当成 INP,会得到一个完全不同、也无法与行业口径比较的数字。
TBT 与 TTI
TBT(Total Blocking Time)是 FCP 到 TTI 之间,所有长任务超过 50 毫秒部分的累加。它是一个实验室指标,依赖可控的加载条件,适合做版本间对比,不适合直接代表真实用户的某一次体验。
TTI(Time to Interactive)表示页面已经完成有用的首次绘制、主线程足够空闲、能够稳定响应输入的时刻。TTI 的判定依赖长任务窗口,不同工具的实现细节可能不同,因此跨工具比较前要确认算法版本。
TBT 与 INP 的关系常被误解:TBT 高通常意味着主线程被占满,INP 也可能变差;但 TBT 低不代表 INP 一定好,因为交互期间的长任务、事件处理逻辑和渲染成本仍可能拖慢单次响应。实验室看 TBT,真实用户看 INP,两者不能互相替代。
CLS
CLS(Cumulative Layout Shift)衡量页面内容的意外位移。它由影响区域与位移距离共同决定,并按会话窗口聚合,避免把长时间页面里的偶发位移无限累加。
口径里必须排除用户主动触发的位移,例如用户点击后展开的菜单、用户滚动后出现的吸顶栏。把这些算进 CLS,会让“交互丰富”的页面被误判为不稳定。另一个常见错误是把字体加载导致的文本重排当成正常现象:只要用户没有预期内容会移动,它就应计入。
端上特有指标
Web 指标之外,原生与混合客户端还有一组必须单独定义的口径:
| 指标 | 测量对象 | 关键边界 |
|---|---|---|
| 冷启动时长 | 进程不存在到首帧可交互 | 起点是系统创建进程,终点是首屏可操作 |
| 温启动时长 | 进程存活但页面被回收 | 起点是回到前台,终点是页面恢复可操作 |
| 热启动时长 | 进程与页面都存活 | 只测前台切换,不测业务恢复 |
| 崩溃率 | 会话或用户维度 | 必须声明分母是会话、用户还是启动次数 |
| ANR 率 | 主线程长时间无响应 | 不同系统的判定阈值不同,不能直接横比 |
| 慢帧与冻结帧 | 帧耗时超过阈值 | 阈值要写进字典,不能口头约定 |
| 内存峰值 | 前台峰值与后台峰值 | 要区分虚拟内存、常驻内存与系统压力指标 |
| OOM 率 | 被系统因内存被杀 | 与普通崩溃分开统计,否则归因会失真 |
这些指标没有统一的行业定义,更容易出现“各说各话”。把它们写进指标字典,比争论哪个数字更准确更重要。
一张关系表
| 指标 | 类型 | 起点 | 终点 | 主要回答 |
|---|---|---|---|---|
| FP | 实验室/真实用户 | 导航开始 | 首次任意绘制 | 页面是否开始渲染 |
| FCP | 实验室/真实用户 | 导航开始 | 首次内容绘制 | 用户是否看到内容 |
| LCP | 真实用户为主 | 导航或路由开始 | 最大内容元素绘制 | 主要内容是否可见 |
| INP | 真实用户 | 交互输入 | 下一帧绘制 | 交互是否跟手 |
| TBT | 实验室 | FCP | TTI | 主线程被阻塞多久 |
| CLS | 真实用户为主 | 页面可见 | 页面隐藏 | 内容是否意外跳动 |
| 冷启动 | 真实用户/实验室 | 进程创建 | 首屏可操作 | 应用打开是否快 |
| 崩溃率 | 真实用户 | 会话或启动开始 | 会话结束 | 应用是否稳定 |
采样、聚合与分位
分母决定含义
“成功率 99%”本身没有意义,必须问分母是什么。以请求为分母,重试会把分母放大;以会话为分母,一个会话里的多次失败只算一次;以用户为分母,重度用户会稀释问题。客户端指标尤其容易在这里出错,因为一次页面访问可能产生几十个请求、多次交互和多个路由。
建议每个指标都显式写出三层分母:事件层(多少次请求/交互)、会话层(多少次访问)、用户层(多少个独立用户)。排查阶段看事件层,评估影响面看会话层,做长期趋势看用户层。三层都看,才能避免“请求成功率很高,但受影响用户很多”这类误判。
分位而不是平均
体验指标天然是长尾分布。多数用户很快,少数用户极慢,平均值会被两端拉扯,既不像典型用户,也不像最差用户。常用做法是同时看 P50、P75、P95:P50 代表典型体验,P75 接近“多数人可接受”的边界,P95 暴露尾部问题。
具体用哪个分位做预算,是团队约定,不是自然规律。重要的是固定下来,并在指标字典里写清楚。今天用 P75、明天用 P90,趋势图就会失去意义。
分位不能求平均
一个常见错误是把多个维度的 P75 求平均,得到“总体 P75”。分位数不具备可加性,正确的做法是保留原始分布或直方图,在总体样本上重新计算分位。若只能拿到各维度的分位值,至少不要把它们平均后当成总体指标,而应标注为“维度分位的均值”,并说明它不是总体分位。
同理,按天 P75 的周平均也不是周 P75。看板可以做平滑,但不能把平滑后的数当成原始口径对外汇报。
样本量与尾部
分位越靠尾部,需要的样本越多。P95 在小样本上会剧烈跳动,P99 更容易被个别极端值支配。展示分位时同时展示样本数,是最低要求;样本不足时,宁可退回 P50/P75,也不要用一个不稳定的 P99 做结论。
另一个细节是采样率。端上为了控制成本,常按会话或用户采样。采样后的分位仍然可以估计总体分位,但前提是采样与服务端无关,且各版本的采样率一致。如果新版本降低了采样率,趋势图上的“改善”可能只是样本构成变了。
端上采样与服务端采样的差异
| 维度 | 端上采样 | 服务端采样 |
|---|---|---|
| 采样单位 | 会话、用户、设备 | 请求、连接、追踪 ID |
| 丢失环节 | 应用被杀、离线、上报失败 | 日志管道、采样丢弃 |
| 偏差来源 | 设备性能、网络、版本分布 | 流量结构、重试、爬虫 |
| 适合回答 | 真实体验、崩溃、卡顿 | 接口耗时、错误率、容量 |
| 不适合回答 | 服务端容量与成本 | 用户感知与端上渲染 |
端上数据和服务端数据不能互相替代。端上 LCP 包含了网络、资源加载、解析与渲染;服务端只看到请求处理的一段。把服务端接口耗时当成首屏耗时,会系统性低估问题。
端上口径与服务端口径的差异
同样是“一次请求的耗时”,两边的计时边界不同。服务端通常从收到完整请求开始,到写出响应结束;端上从发起请求开始,包含 DNS、连接、TLS、排队、传输和等待,直到响应体读完。弱网下,端上耗时可能是服务端耗时的数倍,这不是谁算错了,而是测的不是同一段。
| 维度 | 端上口径 | 服务端口径 |
|---|---|---|
| 计时起点 | 用户动作或导航开始 | 请求到达或连接建立 |
| 计时终点 | 像素绘制或交互完成 | 响应写出完成 |
| 失败样本 | 可能完全丢失 | 通常有日志 |
| 时钟 | 设备时钟,可能漂移 | 服务器时钟,可校准 |
| 网络 | 真实且多变 | 机房内相对稳定 |
| 重复 | 重试、双端上报 | 重试、代理重放 |
| 版本 | 新旧长期共存 | 通常同时一致 |
这张表的用途不是比较谁更准,而是提醒:当端上说“慢”、服务端说“快”时,先确认两边测的是不是同一段路径。多数争论在把边界画出来后就会消失。
常见统计陷阱
平均值掩盖尾部
案例假设值:某页面加载耗时平均 1.2 秒,看起来可以接受;但 P95 是 8 秒,意味着每二十次访问就有一次明显卡顿。平均值把一次 30 秒的极端值摊薄了,也把大量 0.8 秒的快速样本当成了“整体不错”的证据。
处置方式很简单:默认看分位,平均值只作为辅助;同时在图表上标出样本数和尾部截断规则。
幸存者偏差
只有成功加载并成功上报的会话才会进入 LCP 样本。白屏、崩溃、用户立刻退出的会话往往没有指标,于是“留下来的样本”看起来都不错。案例假设值:白屏率 2%,但白屏会话全部缺失 LCP,报表上的 LCP 仍然很好看。
处置方式是给每个体验指标配一个“应上报数”分母,例如页面曝光数或启动数,用上报覆盖率反推缺失。缺失率本身就是一个指标,不能只在上报成功的数据里做分析。
埋点丢失
端上埋点可能因为进程被杀、离线、队列满、上报失败而丢失。丢失往往不是随机的:低端机更容易被杀,弱网更容易失败,出问题的版本更容易崩溃。于是丢失本身带有偏差,直接把剩余样本当成全体,会系统性高估体验。
处置方式包括:本地队列持久化、下次启动补报、关键事件双通道、用服务端请求数做交叉校验。补报数据要带原始时间戳和补报标记,避免把“补报时刻”当成“发生时刻”。
重复与双端上报
同一个事件可能被 Web 容器和原生容器各上报一次,也可能因为重试被重复发送。重复样本会让某些路径的权重翻倍,尤其是失败重试多的路径,恰好是最需要准确归因的路径。
处置方式是在事件里带稳定的去重键,并在入库时按去重键聚合;同时监控重复率,把它当成数据质量指标。
后台与不可见页面
页面进入后台后,浏览器会节流定时器、暂停渲染,LCP 和 CLS 也停止更新。如果把后台停留时间算进“页面加载时长”,会得到大量虚假的慢样本。反过来,如果只在页面隐藏时上报,又会漏掉那些一直不隐藏的长会话。
处置方式是明确“可见时长”和“墙钟时长”两个口径,并在指标字典里写清楚用哪一个。涉及首屏的指标,一律只统计可见阶段。
口径漂移
口径漂移很少以“改定义”的形式出现,更多是悄悄发生的:新版本换了采集 SDK、改了路由结构、把某个页面从 SSR 改成 CSR、调整了采样率。指标曲线变了,团队却以为体验变了。
处置方式是给指标字典加版本号,采集实现变更时记录变更点,并在看板上标注发布时间。跨版本比较前,先确认口径一致;不一致时,宁可分段看,也不要连成一条线。
选择偏差
只统计内测用户、只统计新设备、只统计某个渠道,都会得到有偏的结论。案例假设值:内测用户的设备普遍较新,LCP 比全量用户好一截;如果按内测数据定预算,上线后必然超标。
处置方式是明确样本范围,并按设备档位、网络类型、版本、渠道做分层。总体指标用于汇报,分层指标用于决策。
指标字典模板
指标字典不需要复杂工具,一段固定结构的文本就能避免大部分争论。建议每个指标至少包含以下字段:
指标名:
英文名 / 缩写:
一句话定义:
单位:
计时起点:
计时终点:
采集位置(端上 / 服务端 / 实验室):
聚合方式(P50 / P75 / P95 / 均值):
分母(事件 / 会话 / 用户):
分层维度(版本 / 设备 / 网络 / 路由):
预算或阈值:
已知偏差与缺失:
负责人:
口径版本与变更记录:
填写示例
以 LCP 为例,一份可用的条目大致如下:
| 字段 | 示例内容 |
|---|---|
| 指标名 | 主要内容可见时间 |
| 英文名 | Largest Contentful Paint |
| 一句话定义 | 视口内最大内容元素完成绘制的时刻 |
| 单位 | 毫秒 |
| 计时起点 | 导航开始或路由切换开始 |
| 计时终点 | 最大内容元素绘制完成 |
| 采集位置 | 端上真实用户 |
| 聚合方式 | 按天取 P75 |
| 分母 | 页面曝光会话 |
| 分层维度 | 版本、设备档位、网络类型、路由 |
| 预算或阈值 | 案例假设值:P75 不超过 2.5 秒 |
| 已知偏差 | 白屏与崩溃会话缺失;后台页面停止记录 |
| 负责人 | 客户端性能小组 |
| 口径版本 | v1,随采集 SDK 变更复核 |
这张表里的 2.5 秒是案例假设值,不是行业标准。团队应根据自己的产品类型、设备分布和用户预期确定预算,并把依据写进决策记录。
速查表
| 指标 | 回答的问题 | 单位 | 起点 | 终点 | 常用聚合 | 主要偏差 |
|---|---|---|---|---|---|---|
| FP | 是否开始渲染 | 毫秒 | 导航开始 | 首次任意绘制 | P75 | 背景色也会触发 |
| FCP | 是否出现内容 | 毫秒 | 导航开始 | 首次内容绘制 | P75 | 骨架屏会美化结果 |
| LCP | 主内容是否可见 | 毫秒 | 导航/路由开始 | 最大内容绘制 | P75 | 交互终止更新;后台停止 |
| INP | 交互是否跟手 | 毫秒 | 输入事件 | 下一帧绘制 | 高分位 | 不包含请求完成时间 |
| TBT | 主线程阻塞程度 | 毫秒 | FCP | TTI | 实验室中位数 | 依赖可控加载条件 |
| CLS | 内容是否跳动 | 无量纲 | 页面可见 | 页面隐藏 | 会话窗口 | 用户触发的位移应排除 |
| 冷启动 | 打开是否快 | 毫秒 | 进程创建 | 首屏可操作 | P75/P95 | 设备与系统差异大 |
| 崩溃率 | 是否稳定 | 百分比 | 会话/启动开始 | 会话结束 | 按版本分桶 | 分母定义必须固定 |
| 慢帧率 | 滚动是否流畅 | 百分比 | 页面可见 | 页面隐藏 | 按设备分层 | 帧率上限随设备不同 |
| 内存峰值 | 是否接近上限 | MB | 页面进入 | 页面退出 | P95 | 系统压力指标不等于占用 |
使用这份速查的三个场景
第一个场景是定预算。产品、设计和工程一起把候选指标过一遍,删掉回答不了当前问题的指标,给留下的每个指标填写字典字段。预算不是拍一个数字,而是回答“超过多少就算失败、失败后谁负责”。
第二个场景是排查。先确认症状对应哪个指标,再确认该指标的起点、终点和分母,最后才看数据。顺序反过来,很容易拿着一份口径不对的报表找错方向。
第三个场景是评审。附录 C 的评审清单会引用这里的指标字典;没有字典的团队,评审时只能讨论“感觉快不快”,无法判断质量预算是否可执行。
常见错误与边界
- 把 FP 或 FCP 当成首屏完成。它们只说明“有东西出现”,不说明主要内容可见。
- 用平均值汇报体验。平均值既不代表典型用户,也不代表最差用户。
- 把多个维度的 P75 求平均。分位不可加,必须回到原始分布重算。
- 用服务端接口耗时代表端上首屏。两边计时的起止点不同,网络与渲染都不在服务端口径里。
- 只统计上报成功的样本。白屏、崩溃、立即退出的会话恰恰是最需要被看见的部分。
- 把补报时间当成发生时间。补报数据必须保留原始时间戳。
- 不写分母。会话、用户、请求三种分母会给出三个不同的“成功率”。
- 口径变更不留记录。曲线突变时,无法区分是体验变了还是采集变了。
- 把案例假设值当成行业标准。阈值要结合产品事实,并写进决策记录。
- 指标越多越好。回答不了决策问题的指标,只会增加维护成本。
小结
指标的价值不在数字本身,而在它固定了一套可复现的观察方式。定义清楚起点、终点、分母和聚合方式,跨团队才有共同语言;保留原始分布、标注样本量与缺失,才能避免被平均值和幸存者偏差误导。
这份速查表可以直接贴进评审文档,也可以作为指标字典的起点。真正要落到团队里的,不是这张表,而是“每个指标都有负责人、有口径版本、有变更记录”的习惯。下一份附录把这些指标用到具体症状上,给出端上问题的排查顺序。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。