一、为什么需要监控页面性能
前端页面性能是一个非常核心的用户体验指标。一个页面性能差的话会大大影响用户体验。用户打开页面等待的太久(5秒以内是比较优秀的,10秒以内还是可以忍受的,超过十秒用户一般回选择直接关闭页面),可能会直接关掉页面,甚至就不再使用了。
二、页面性能指标
规范:
- Level 1 规范
如下图是W3C Navigation Timing Level 1 的规范,2012 年底进入候选建议阶段,至今仍在日常使用中。
从当前浏览器窗口卸载旧页面开始,到新页面加载完成,整个过程一共被切分为 9 个小块:提示卸载旧文档、重定向/卸载、应用缓存、DNS 解析、TCP 握手、HTTP 请求处理、HTTP 响应处理、DOM 处理、文档装载完成。每个小块的首尾、中间做事件分界,取 Unix 时间戳,两两事件之间计算时间差,从而获取中间过程的耗时(精确到毫秒级别)。
- Level 2 规范
Level 2精度更高,功能更强大,层次更分明的。比如独立划分出来的 Resource Timing,可以获取具体资源的详细耗时信息。
一般来说,我们需要获取到的页面性能参数包括:DNS查询耗时
、TCP链接耗时
、request请求耗时
、解析dom树耗时
、白屏时间
、domready时间
、onload时间
等,而这些参数是通过 performance.timing
各个属性的差值组成的。
指标解读:
字段名称 | 含义 |
---|---|
navigationStart | 表示从上一个文档卸载结束时的unix时间戳,如果没有上一个文档,这个值和fetchStart值相等 |
unloadEventStart | 表示当前网页(与当前页面同域)unload的时间戳,如果没有前一个网页的unload或者不在同一个域,值为0 |
unloadEventEnd | 返回前一个unload时间绑定的回调函数执行完毕的时间戳 |
redirectStart | 第一个HTTP重定向发生的时间。有跳转且是同域名内的部的重定向才算,否则值为0 |
redirectEnd | 最后一个HTTP重定向完成的时间。有跳转且是同域名内的部的重定向才算,否则值为0 |
domainLookupStart/domainLookupEnd | DNS域名查询开始/结束的时间,如果使用了本地缓存(即无DNS查询)或持久连接,则与fetchStart值相等 |
connectStart | HTTP(TCP)开始/重新建立连接的时间,如果是持久连接,则与fetchSatrt值相等 |
connectEnd | HTTP(TCP)完成建立连接的时间(完成握手),如果是持久连接,则与fetchSatrt值相等 |
fetchStart | 浏览器准备好使用HTTP请求抓取文档的时间,这放生在检查本地缓存之前 |
requestStart | HTTP请求读取真实文档开始的时间(完成建立连接),包括从本地缓存读取 |
responseStart | HTTP开始接收响应的时间(获取到第一个字节),包括从本地缓存读取 |
responseEnd | HTTP响应全部接受完成的时间(获取到最后一个字节),包括从本地缓存读取 |
domLoading | 开始解析渲染DOM树的时间, 此时Document.readyState变为loading,并抛出readystatechange相关事件 |
domInteractive | 完成解析DOM树的时间,Document.readyState变为interactive,并抛出readystatechange相关事件,注意:只是DOM树解析完成,并没有开始加载网页内的资源 |
domContentLoadedEventStart | DOM树解析完成后,网页内资源加载开始的时间,在DOMContentLoaded事件抛出之前 |
domContentLoadedEventEnd | DOM解析完成后,网页内资源加载完成的时间(如JS脚本加载执行完毕) |
domComplete | DOM树解析完成,且资源也准备就绪的时间, Document.readyState变为complete,并抛出readystatechange相关事件 |
确定统计起始点( navigationStart vs fetchStart)
页面性能统计的起始点时间,应该是用户输入网址回车后开始等待的时间。一个是通过navigationStart获取,相当于在URL输入栏回车或者页面按F5刷新的时间点;另外一个是通过 fetchStart,相当于浏览器准备好使用 HTTP 请求获取文档的时间。
从开发者实际分析使用的场景,浏览器重定向、卸载页面的耗时对页面加载分析并无太大作用;通常建议使用 fetchStart 作为统计起始点。
首字节
主文档返回第一个字节的时间,是页面加载性能比较重要的指标。对用户来说一般无感知,对于开发者来说,则代表访问网络后端的整体响应耗时。
白屏时间
用户看到页面展示出现一个元素的时间。很多人认为白屏时间是页面返回的首字节时间,但这样其实并不精确,因为头部资源还没加载完毕,页面也是白屏。
相对来说具备「白屏时间」统计意义的指标,可以取domLoading - fetchStart
,此时页面开始解析DOM树,页面渲染的第一个元素也会很快出现。
从W3C Navigation Timing Level 2 的方案设计,可以直接采用domInteractive-fetchSatart
此时页面资源加载完成,即将进入渲染环节。
首屏时间
首屏时间是指页面第一屏所有资源完整展示的时间。这是一个对用户来说非常直接的体验指标,但是对于前端却是一个非常难以统计衡量的指标。
具备一定意义上的指标可以使用,domContentLoadedEnd-fetchStart
,甚至使用loadEventStart - fetchSatrt
,此时页面DOM树已经解析完成并且显示内容。
统计页面性能指标的方法:
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 || t.fetchStart);
//DNS 解析耗时
times.appcacheTime = t.domainLookupStart - t.fetchStart;
//卸载页面的时间
times.unloadTime = t.unloadEventEnd - t.unloadEventStart;
//tcp连接耗时
times.tcpTime = t.connectEnd - t.connectStart;
//SSL 安全连接耗时
times.sslTime = t.connectEnd - t.secureConnectionStart;
//request请求耗时(TTFB)
times.reqTime = t.responseStart - t.requestStart;
//解析dom树耗时
times.analysisTime = t.domComplete - t.domInteractive;
//白屏时间
times.blankTime = t.domInteractive - t.fetchStart;
//domReadyTime
times.domReadyTime = t.fetchStart - t.domContentLoadedEventEnd ;
// 页面完全加载时间 loadEventStart - fetchStart
// http 头部大小: transferSize - encodedBodySize
// 重定向次数:performance.navigation.redirectCount
// 重定向耗时: redirectEnd - redirectStart
注意⚠️
通过window.performance.timing
所获的的页面渲染所相关的数据,在SPA应用中改变了url但不刷新页面的情况下是不会更新的。因此仅仅通过该api是无法获得每一个子路由所对应的页面渲染的时间。如果需要上报切换路由情况下每一个子页面重新render的时间,需要自定义上报。
三、如何上报性能指标
测量好时间后,就需要将数据发送给服务端。页面性能统计数据对丢失率要求比较低,且性能统计应该在尽量不影响主流程的逻辑和页面性能的前提下进行。
- 使用的img标签get请求
优点 :不存在ajax跨域问题,可以做跨域请求;没有浏览器兼容问题
let img = new Image();
img.onload = img.onerror = img.onabort = function () {
img = img.onload = img.onerror = img.onabort = null;
}
img.src = url;
- navigator.sendBeacon
大部分现代浏览器都支持navigator.sendBeacon
方法。这个方法可以用来发送一些统计和诊断的小量数据,特别适合上报统计的场景。
优点:数据可靠,浏览器关闭请求也照样能发;异步执行,不会影响下一页面的加载;API使用简单;
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon("/log", analyticsData);
}
当浏览器支持sendBeacon方法,优先使用该方法,否则使用img方式降级上报。