页面性能监控中”资源加载”指标的深度解析:为什么表格和总和对不上?

star 2小时前 ⋅ 9 阅读
ad

在前端性能监控中,你是否遇到过这样的困惑:页面详情页顶部显示”资源加载”耗时3500ms,但下方资源列表里每个资源只有十几毫秒?这到底是怎么回事?本文将带你深入理解浏览器性能指标的采集原理和统计逻辑。

一、问题现象

在上述页面详情界面中,我们可以清晰地看到一个典型现象:

  • 顶部”资源加载 Resource Load”:显示为 3500ms(用红色框标注)
  • 下方”资源加载列表”
    • 第一张图片:11ms
    • 第二张图片:9ms

问题:为什么顶部显示3500ms,但表格里所有资源的加载时间加起来才20ms左右?这难道是一个bug吗?

答案这是正常的! 下面我们来深入理解其中的原理。

二、这3500ms到底代表什么?

2.1 计算来源:两套完全不同的采集机制

关键理解:瀑布图的”资源加载 3500ms”和资源列表来自两套完全不同的数据采集机制

① 瀑布图的”资源加载”来自 Navigation Timing API

// pageView.js Lines 54-62
var pageCompleteLoaded = utils.perfSubtract(t.loadEventStart, fetchStart);
...
var resourceLoaded = utils.perfSubtract(t.loadEventStart, t.domContentLoadedEventEnd);

t.loadEventStart 和 t.domContentLoadedEventEnd 是浏览器级别的时间戳,记录的是页面上所有资源(包括图片、字体、脚本、XHR、iframe等)全部加载完毕的真实时间,浏览器保证这个值是准确的

② 资源列表来自 PerformanceObserver 监听

// index.js Lines 45-63
var handleEntries = function(entries) {
 for (var i = 0; i < entries.length; i++) {
 var entry = entries[i];
 if (entry.entryType === 'resource') {
 ...
 // 过滤掉接口请求(xmlhttprequest 和 fetch),因为它们已经在 HTTP 模块中统计过了
 if (entry.initiatorType === 'xmlhttprequest' || entry.initiatorType === 'fetch') {
 continue;
 }
 }
 }
}

SDK用 PerformanceObserver 监听 resource 类型的条目,这是另一套采集机制

2.2 时间窗口的含义

这3500ms对应的是浏览器性能时间轴上的一个阶段:从 DOMContentLoaded 已经跑完,到 window 的 load 即将触发之前这一段。

可以理解为:

  • HTML解析和”会挡住DCL的那类事”基本告一段落
  • 到浏览器认为”这次导航该带的子资源都收尾了”
  • 中间那一段墙钟时间(Wall-clock Time)

2.3 这段时间里,浏览器在忙什么?

在这段时间里,常见正在发生的事包括(不一定每一项都有,但往往是这些的组合):

1️⃣ 还在拉、还在处理”算进这次页面load的子资源”

例如仍在进行或刚结束的:

  • 图片(尤其大图、多图、未懒加载的图)
  • 样式表(若仍参与本次导航的完成判定)
  • iframe 子页面加载
  • 仍在排队或重试的脚本/字体(取决于插入方式和浏览器调度)

关键点load 事件会等待参与这次文档加载完成判定的那批子资源;其中只要有一个慢,整段 DCL→load 就会被拉得很长,哪怕列表里别的资源只有十几毫秒。

2️⃣ 并行与”窗口”不等于”列表相加”

很多请求是并行的。列表里每条是”单个资源自己的耗时”;上面的3500ms是”从DCL结束到load开始这一整段日历时间有多长”。

所以会出现:阶段3.5秒,但你看到的几条小图各十几毫秒——中间可能还有你没在截图里看到的资源,或有慢资源已结束但列表未展示/被过滤。

3️⃣ 主线程忙,网络其实早就完了(或混在一起)

有时网络下载并不慢,但:

  • 大量JS在执行(大bundle、defer之后仍很重的逻辑、同步长任务)
  • 样式计算、布局、解析很重

这些会挤占主线程,让”收尾”变慢;用户体感像”资源阶段很长”,但Performance里DCL→load这段仍会把等待子资源+浏览器完成加载算法相关步骤的时间算进去(具体以浏览器实现为准,但现象上很常见)。

4️⃣ load之前还可能有的”空等”

例如:

  • 连接排队 / HTTP/1.1 队头阻塞
  • 低优先级请求被延后
  • 某个资源慢、拖住整个load窗口

三、为什么对不上是正常的?

1. 指标含义不同(最常见)

顶部的「资源加载」一般是页面级时间轴上的一段区间,例如类似”从DOM可交互前后到load事件前后”这一整段里,浏览器还在拉子资源的时间窗口。

表格里每一行的「加载时间」通常是单个资源从发起请求到结束(或等价于Performance Resource Timing的duration)的耗时。

一段窗口时间的长度,并不等于”表格里所有行耗时的相加”——尤其资源是并行加载时,多行相加往往会大于真实墙钟时间;反过来,若顶部取的是”整段窗口”而列表只收录了部分资源,也会出现窗口很大、表里数字很小的情况。

2. 列表很可能不完整

截图里只看到两条小图,列表多半可滚动;若下面还有脚本、样式、字体、XHR、大图等,未展示行的耗时会改变你对”总和”的直觉,但仍不一定等于顶部那个3500ms(原因见上一条)。

3. 采集范围不一致

下方列表有时会过滤:只展示静态资源、只展示某域名、不展示跨域无Timing的资源、不展示iframe内资源等;而顶部”资源加载”阶段仍可能把整页在该阶段的等待都算进去,于是出现阶段很长、表里只有几条小资源且很快的观感。

4. 主线程/解析与”网络耗时”混在一起

若产品把”资源加载阶段”定义得偏宽,可能把排队、阻塞、低优先级请求延后等都算进这段时间里;而表格里单个img的”加载时间”往往只是该请求自身的计时,两者不对齐就会产生阶段3.5s、单条只有十几毫秒的反差。

四、什么时候算”不合理”、值得怀疑?

虽然”对不上”通常是正常的,但以下情况确实值得怀疑:

  • 产品文案或文档明确写了:顶部「资源加载」= 下方列表各资源耗时的合计,且你确认列表是全量的,那3500ms与约20ms就矛盾,更像是统计口径错误或实现bug。

  • 若列表全量且资源很少、都很小很快,而顶部长期固定偏大(例如总像凑整到某值),也值得查是否重复计时、是否减错了起止点。

五、资源漏采的三大根因

5.1 问题的本质

这个页面(KingSoft Work)是复杂的Web应用,资源数量极多,SDK的资源列表只有2条(11ms、9ms),说明绝大多数资源没有被捕获到。这是由以下三大原因共同导致的:

5.2 原因一:浏览器Performance缓冲区满了被清空

浏览器默认只保留 150条 资源timing记录(resourceTimingBufferSize 默认值)。KingSoft Work这类复杂应用可能在SDK初始化时,早期加载的大量资源已经被挤出缓冲区,getEntriesByType('resource') 只能拿到剩余的少数条目。

// index.js Lines 131-133
// 1. 获取已经存在的资源
handleEntries(window.performance.getEntriesByType('resource'));
// 2. 监听后续加载的资源

SDK初始化前加载的资源,如果缓冲区已满,getEntriesByType 拿不到,PerformanceObserver 也还没挂上,这段时间的资源彻底丢失

5.3 原因二:SDK过滤掉了未知类型的资源

// index.js Lines 86-95
var elementType = getResourceType(url, entry.initiatorType);
// 过滤掉无法识别的资源类型
if (!elementType) continue;

getResourceType 只认 script/link/img 三类,其他类型(iframe、video、audio、object以及各种自定义preload等)全部返回 null 被过滤掉。

5.4 原因三:XHR/Fetch被主动过滤

页面初始化时若有缓慢的接口请求(比如加载文档内容的API),这些请求确实会阻止 load 事件触发,从而拉长”资源加载”阶段,但SDK的资源列表刻意不采集它们(因为已经在HTTP监控模块里采集了)。

5.5 总结:3500ms花在哪了

真实耗时来源 是否出现在资源列表
页面的大量JS/CSS bundle(缓冲区满了被丢弃) ❌ 看不到
未知类型资源(iframe、video等) ❌ 过滤掉了
慢速初始化API请求(XHR/Fetch) ❌ 刻意过滤
SDK成功捕获的img资源(11ms、9ms) ✅ 能看到

所以列表里看起来只有两个小图片,但瀑布图显示3500ms完全是正常的,两者测量的根本就不是同一个集合。

六、如何正确理解和排查性能问题?

6.1 正确的排查思路

  1. 先确认顶部3500ms的构成

    • 是否有大型图片未懒加载?
    • 是否有阻塞渲染的同步脚本?
    • 是否有iframe或复杂样式计算?
  2. 查看完整的资源列表

    • 向下滚动查看所有资源
    • 检查是否有大图、脚本、样式等耗时资源
    • 注意筛选条件是否过滤了关键资源
  3. 结合Performance面板

    • 使用浏览器DevTools的Performance面板
    • 查看完整的加载时间线
    • 定位到底是网络慢还是主线程忙

6.2 优化建议

除了常规的性能优化手段,还需要关注SDK采集的完整性:

① 优化SDK采集逻辑

  • 扩大Performance缓冲区:在SDK初始化时设置更大的 resourceTimingBufferSize
  • 提前初始化SDK:尽量在页面最早阶段初始化,减少资源丢失
  • 使用PerformanceObserver替代getEntriesByType:监听所有资源,避免缓冲区限制

② 前端性能优化

  • 图片优化:使用懒加载、WebP格式、CDN加速
  • 脚本优化:使用defer/async、代码分割、减少同步脚本
  • 样式优化:避免使用@import、关键CSS内联、异步加载非关键CSS
  • 资源优先级:合理使用preload/prefetch、设置合适的cache策略
  • 监控完善:确保关键资源都被正确采集和展示

七、总结

页面性能监控中的”资源加载”指标看似简单,实际上包含了复杂的时间窗口计算和资源采集逻辑。

核心要点

  1. ✅ 顶部”资源加载”来自Navigation Timing API,是浏览器级别的时间戳,准确可靠
  2. ✅ 资源列表来自PerformanceObserver,是另一套采集机制,有过滤和限制
  3. ✅ 两套机制测量的不是同一个集合,”对不上”完全正常
  4. ✅ 资源列表不完整的主要原因是:缓冲区限制、类型过滤、主动过滤XHR/Fetch
  5. ✅ 并行加载会让窗口时间远小于各资源耗时之和
  6. ✅ 主线程忙碌也会被计入资源加载阶段

理解这些原理后,我们就能更准确地分析性能瓶颈,而不是被表面的数字所迷惑。性能监控的本质是帮助我们发现问题、定位瓶颈,而不是纠结于数字的绝对精确。

参考文档

关于Webfunny

Webfunny专注于前端监控系统,前端埋点系统的研发。 致力于帮助开发者快速定位问题,帮助企业用数据驱动业务,实现业务数据的快速增长。支持H5/Web/PC前端、微信小程序、支付宝小程序、UniApp和Taro等跨平台框架。实时监控前端网页、前端数据分析、错误统计分析监控和BUG预警,第一时间报警,快速修复BUG!支持私有化部署,Docker容器化部署,可支持千万级PV的日活量!

  点赞 0   收藏 0
  • star
    共发布6篇文章 获得0个收藏
全部评论: 0