(转载)10分钟彻底搞懂前端页面性能监控

原文链接 https://juejin.im/post/5d8cc378f265da5ba0776f36

前言

前端页面性能是一个非常核心的用户体验指标。本文介绍阿里 UC 岳鹰全景监控平台 如何设计一个通用、低侵入性、自动上报的页面性能监控方案。主要采用的是 Navigation Timing API 以及 sendBeacon 等方法。

导读

为什么要监控页面性能

一个页面性能差的话会大大影响用户体验。用户打开页面等待的太久,可能会直接关掉页面,甚至就不再使用了,这种情况在移动端更加明显,移动端用户对页面响应延迟容忍度很低。

虽然页面性能很重要,但是在实际使用中,页面性能差的情况并不少见。首先,在产品的迭代演进过程中,页面性能可能会被忽略,性能随着版本迭代而有所衰减;其次,性能优化是一项复杂而挑战的事情,需要明确的优化方向和具体的优化手段才能快速落地取效。

所以我们需要一个性能监控系统,持续监控和预警页面性能的状况,并且在发现瓶颈的时候指导优化工作。

理解 Navigation Timing API 的性能指标

为了帮助开发者更好地衡量和改进前端页面性能,W3C 性能小组引入了 Navigation Timing API,实现了自动、精准的页面性能打点;开发者可以通过 window.performance 属性获取。

  • performance.timing 接口(定义了从 navigationStartloadEventEnd 的 21 个只读属性)

  • performance.navigation(定义了当前文档的导航信息,比如是重载还是向前向后等)

下图是 W3C 第一版的 Navigation Timing 的处理模型。从当前浏览器窗口卸载旧页面开始,到新页面加载完成,整个过程一共被切分为 9 个小块:提示卸载旧文档、重定向/卸载、应用缓存、DNS 解析、TCP 握手、HTTP 请求处理、HTTP 响应处理、DOM 处理、文档装载完成。每个小块的首尾、中间做事件分界,取 Unix 时间戳,两两事件之间计算时间差,从而获取中间过程的耗时(精确到毫秒级别)。

Level 1

上图是 Level 1 的规范,2012 年底进入候选建议阶段,至今仍在日常使用中;但是在 W3C 的议程上,它已经功成身退,让位给了精度更高,功能更强大,层次更分明的 Level 2(处理模型如下图)。比如独立划分出来的 Resource Timing,使得我们可以获取具体资源的详细耗时信息。

Level 2

指标解读

指标解读

采集页面性能的关键指标

使用上面的指标,我们可以计算许多重要的指标,如首字节的时间,页面加载时间,dns 查找以及连接是否安全。我们把 Navigation Timing API 提供的指标做下归类,按照从上到下的时间流,右边的时刻标记了每个指标从哪里开始计算到哪里截止,比如,跳转时间 redirectredirectEnd - redirectStart 计算得到,其他的类推。

关键指标

确定统计起始点 (navigationStart vs fetchStart )

页面性能统计的起始点时间,应该是用户输入网址回车后开始等待的时间。一个是通过 navigationStart 获取,相当于在 URL 输入栏回车或者页面按 F5 刷新的时间点;另外一个是通过 fetchStart,相当于浏览器准备好使用 HTTP 请求获取文档的时间。

从开发者实际分析使用的场景,浏览器重定向、卸载页面的耗时对页面加载分析并无太大作用;通常建议使用 fetchStart 作为统计起始点。

首字节

主文档返回第一个字节的时间,是页面加载性能比较重要的指标。对用户来说一般无感知,对于开发者来说,则代表访问网络后端的整体响应耗时。

白屏时间

用户看到页面展示出现一个元素的时间。很多人认为白屏时间是页面返回的首字节时间,但这样其实并不精确,因为头部资源还没加载完毕,页面也是白屏。

相对来说具备「白屏时间」统计意义的指标,可以取 domLoading - fetchStart,此时页面开始解析 DOM 树,页面渲染的第一个元素也会很快出现。

从 W3C Navigation Timing Level 2 的方案设计,可以直接采用 domInteractive - fetchStart,此时页面资源加载完成,即将进入渲染环节。

首屏时间

首屏时间是指页面第一屏所有资源完整展示的时间。这是一个对用户来说非常直接的体验指标,但是对于前端却是一个非常难以统计衡量的指标。

具备一定意义上的指标可以使用,domContentLoadedEventEnd - fetchStart,甚至使用 loadEventStart - fetchStart,此时页面 DOM 树已经解析完成并且显示内容。
以下给出统计页面性能指标的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
let times = {};
let t = window.performance.timing;

// 优先使用 navigation v2 https://www.w3.org/TR/navigation-timing-2/
if (typeof win.PerformanceNavigationTiming === 'function') {
try {
var nt2Timing = performance.getEntriesByType('navigation')[0];
if (nt2Timing) {
t = nt2Timing;
}
} catch (err) {}
}

//重定向时间
times.redirectTime = t.redirectEnd - t.redirectStart;

//dns 查询耗时
times.dnsTime = t.domainLookupEnd - t.domainLookupStart;

//TTFB 读取页面第一个字节的时间
times.ttfbTime = t.responseStart - t.navigationStart;

//DNS 缓存时间
times.appcacheTime = t.domainLookupStart - t.fetchStart;

//卸载页面的时间
times.unloadTime = t.unloadEventEnd - t.unloadEventStart;

//tcp 连接耗时
times.tcpTime = t.connectEnd - t.connectStart;

//request 请求耗时
times.reqTime = t.responseEnd - t.responseStart;

//解析 dom 树耗时
times.analysisTime = t.domComplete - t.domInteractive;

//白屏时间
times.blankTime = (t.domInteractive || t.domLoading) - t.fetchStart;

//domReadyTime
times.domReadyTime = t.domContentLoadedEventEnd - t.fetchStart;

SPA 盛行之际

Navigation Timing API 可以监控大部分前端页面的性能。但随着 SPA 模式的盛行,类似 vue,reactjs 等框架的普及,页面内容渲染的时机被改变了,W3C 标准无法完全满足原来的监控意义。

幸运的是,目前 W3C 关于首屏统计已经进入了提议阶段,以 Chrome 为首的浏览器正在打造更能代表用户使用体验的 FP、FCP、FMP 指标,并且逐步开放 API。

新的指标

注意点

通过 window.performance.timing 所获的的页面渲染所相关的数据,在 SPA 应用中改变了 url 但不刷新页面的情况下是不会更新的。因此仅仅通过该 api 是无法获得每一个子路由所对应的页面渲染的时间。如果需要上报切换路由情况下每一个子页面重新 render 的时间,需要自定义上报。

数据上报方式

测量好时间后,就需要将数据发送给服务端。页面性能统计数据对丢失率要求比较低,且性能统计应该在尽量不影响主流程的逻辑和页面性能的前提下进行。

使用的 img 标签 get 请求

  • 不存在 AJAX 跨域问题,可做跨源的请求
  • 很古老的标签,没有浏览器兼容性问题
1
2
3
4
5
var i = new Image();
i.onload = i.onerror = i.onabort = function () {
i = i.onload = i.onerror = i.onabort = null;
};
i.src = url;

大部分现代浏览器都支持 navigator.sendBeacon 方法。这个方法可以用来发送一些统计和诊断的小量数据,特别适合上报统计的场景。

  • 数据可靠,浏览器关闭请求也照样能发
  • 异步执行,不会影响下一页面的加载
  • API 使用简单
1
2
3
4
5
window.addEventListener('unload', logData, false);

function logData() {
navigator.sendBeacon('/log', analyticsData);
}

最终方案
当浏览器支持 sendBeacon 方法,优先使用该方法,使用 img 方式降级上报。