Sukka's Blog

童话只美在真实却从不续写

  1. 1. 传统打点和高精准时间规范
  2. 2. Resource Timing 规范和 PerformanceTiming 接口
  3. 3. 计算网页整体加载性能
  4. 4. Google Analytics 统计页面加载性能

在 Performance 对象出现以前,来自神秘的东方的前端开发者们采用一种古老的、带着传奇色彩的、但是极为常见的方法来获取一个网站的资源加载用时 ,你准备好知道了么?

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script>
var start_time = +new Date;
</script>
<link rel="stylesheet" href="style.css">
<script src="jquery.js"></script>
<script>
var end_time_1 = +new Date;
var headtime = end_time_1 - start_time;
</script>
</head>
<body>
<script src="app.js"></script>
<script>
var end_time_2 = +new Date;
var alltime = end_time_2 - start_time
console.log('头部资源用时:' + headtime)
console.log('全页资源用时:' + alltime)
</script>
</body>
</html>

喏,那时的前端开发者就是通过类似这样的方法来获取用时的数据,原理涉及到页面加载和渲染的流程,这些就没必要再讲一遍了。如果你还不知道的话,Google Web Fundamental 上有一个 专门的专题 在讲这个。这种在事件开始前后计时的方法就是打点法,古老但是实用。

传统打点和高精准时间规范

打点法不仅可以用来监测资源加载时间耗时,还可以用来监测 JavaScript 的函数用时。
不过,虽然毫无疑问时间在物理层面上不可以倒流的,但是在用户的设备上是什么都会发生的,一个最简单的例子就是用户修改了时间,当然还可能用户设备的时钟存在偏差、时快时慢。所以如果你直接按照下面的例子这么做,你其实得到的数据可能不准,甚至不一定是正数:

let mark_start = Date.now()
App.MyAwesomeFunc()
let duration = Date.now() - mark_start
console.log(duration)

不过好在 JavaScript 有一个高精准时间规范(High Resolution Time),我们可以用其中的 Performance 对象 来获取精准的时间,所以我们只需要把我们的代码修改成这样:

const mark_start = performance.now()
App.MyAwesomeFunc()
const duration = performance.now() - mark_start
console.log(duration)

Performance 对象还提供了一个获取精确时间戳的可能,而且这个时间不会受到用户设备的时钟误差或者用户修改时间的影响:

const timeStamp = performance.timeOrigin + performance.now()
console.log(timeStamp)

你可以试试在 Console 里跑一个 console.log(Date.now() - performance.timeOrigin - performance.now()) 看高精准时间和「有误差的时间」之间的差距。

Resource Timing 规范和 PerformanceTiming 接口

使用 Performance 对象获取高精准时间使得获取精确的计时成为可能,但是依然有很多问题亟待解决,比如下面两个例子:

  • 页面自身的加载时间通过打点获取不到
  • CSS 中存在 @importbackground 定义的资源是没法打点的
  • 通过 AJAX 和 Beacon 发送的请求也不能简单打点获取到

不过这个问题也已经得到解决,W3C 定义了 资源计时(Resource Timing) 规范,通过 PerformanceTiming 接口可以返回的对象非常详细,包括 DNS 请求、TCP 请求、重定向、传输、编码解码等行为开始和结束的时间。这使得统计每个文件、每个资源、每个请求的性能都成为可能。
当然对于大部分人来说,获取这么多数据其实没有必要——我们最多关心一下页面本身,除了 DNS、TCP 耗时以外还有从页面开始加载到 DOMContentLoaded 事件、load 事件、interactive 事件的用时。这些也是衡量一个网站加载性能的基本参数。我们要做的,也就是将 Performance 对象直接用于 window
在 console 里面打一个 window.performance 就可以看到所有相关信息:

上面就是 PerformanceNavigationTiming 对象的所有时序属性。如果放到请求的流程图中分别对应这几个时间节点。

PerformanceNavigationTiming.png

计算网页整体加载性能

根据 PerformanceNavigationTiming 对象的几个时间点就可以开始计算并获取几个相对重要的时间了。下面是几个常见的时间的计算:

const t = window.performance.timing;
let time = [];

// 页面加载完成的时间
// 从页面开始载入到绑定在 load 事件上的函数全部执行完毕
time.PageLoadTime = t.loadEventEnd - t.navigationStart;

// DOM 用时(包括资源加载和 DOM 树的解析和构建)
times.DOMReady = t.domComplete - t.responseEnd;

// DOM 交互用时
times.DOMInteractiveTime = t.domInteractive - t.domLoading;

// 重定向用时
times.RedirectTime = t.redirectEnd - t.redirectStart;

// DNS 用时
times.DNSTime = t.domainLookupEnd - t.domainLookupStart;

// TTFB 用时
// 注意这里的 TTFB 用时包括了 TCP、SSL、DNS 时间
times.TTFBTime = t.responseStart - t.navigationStart;

// 服务器响应用时
// 同时这也是 Chrome Dev Tools 的对 TTFB 的定义
times.ServerResponseTime = t.responseStart - t.requestStart;

// 页面下载时间
times.PageDownloadTime = t.responseEnd - t.responseStart;

// 从页面加载到 DOMContentLoaded 用时
times.ContentLoadingTime = t.domContentLoadedEventStart - t.navigationStart;

// 在 load 事件上的耗时
times.LoadEventTime = t.loadEvent = t.loadEventEnd - t.loadEventStart;

// 在 TCP 和 SSL 上的耗时
times.TCPTime = t.connectEnd - t.connectStart;

Google Analytics 统计页面加载性能

如果是使用 Google Analytics 的 analytics.js 可以直接通过配置 {'siteSpeedSampleRate': 100} 就可以在访客的每次访问都返回相应的性能数据。如果是使用服务端统计的话需要自行采集、拼凑和回传对应参数。
查看 Google Analytics 的 Measurement Protocol 中,网站速度属于「用户计时」匹配类型(需要设置参数 t=timing )。Google Analytics 在 Measurement Protocol 中定义了支持回传这些数据:

  • plt Page Loading Time 网页加载时间
  • dns DNS 耗时
  • pdt Page Download Time 网页下载时间
  • rrt Redirect Time 重定向时间
  • tcp TCP 连接耗时
  • srt Server Response Time 服务器响应时间
  • dit DOM Interactive Time DOM 可交互时间
  • clt Content Loading Time 内容加载时间

把之前获取到的性能数据赋值上去即可。

let ga_data = [
// 这里 plt 统计的是到 load 事件开始的时间
'plt=' + t.loadEventStart - t.navigationStart,
'dns=' + times.DNSTime,
'pdt=' + times.PageDownloadTime,
'rrt=' + times.RedirectTime,
'tcp=' + times.TCPTime,
'srt=' + times.ServerResponseTime,
'dit=' + times.DOMInteractiveTime,
'clt=' + times.ContentLoadingTime,
];

剩下的就是发送数据了。统计 PV 的请求可以 async 或者安排在 DOMContentLoaded 事件上,采集性能的数据的请求需要放在 load 事件上(早了的话会有一些数据拿不到)。

本文作者 : Sukka
本文采用 CC BY-NC-SA 4.0 许可协议。转载和引用时请注意遵守协议!
本文链接 : https://blog.skk.moe/post/performance_timing/

本文最后更新于 天前,文中所描述的信息可能已发生改变