前端性能统计-首屏时间,白屏时间,用户可操作时间,总下载时间

##主要监控指标

  • DNS查询时间——DNS查询的开始到结束所花费的时间;

  • TCP连接时间——TCP建立连接到连接成功的所花费的时间;

  • 首屏时间——用户浏览器首屏内所有内容都呈现出来所花费的时间;

  • 用户可操作时间(dom Interactive)——用户可以进行正常的点击、输入等操作,默认可以统计domready时间,因为通常会在这时候绑定事件操作;

  • DOM渲染完成时间——代表DOMContentLoaded事件完成的时间节点,此刻用户可以对页面进行操作,也就是页面中的domready时间;

  • 总下载时间——页面所有资源都加载完成并呈现出来所花的时间,即页面 onload 的时间;

  • request请求耗时——从服务器端(或缓存、本地资源)响应回的第一个字节和最后一个字节数据所用的时间;

  • 页面重定向时间——如果页面是由redirect而来,则redirectStart和redirectEnd分别代表redirect开始和结束的时间节点;

  • unload时间——浏览器unload前一个文档的所花费的时间;

##如何统计性能指标的时间

Navigation Timing API 是W3C性能小组引入的新的API,目前IE9以上的浏览器都支持

请求发出的整个过程中,各种环节的时间顺序:

image2018-10-22_19-17-24

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// 获取 performance 数据
var performance = {
// memory 是非标准属性,只在 Chrome 有
// 财富问题:我有多少内存
memory: {
usedJSHeapSize: 16100000, // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize
totalJSHeapSize: 35100000, // 可使用的内存
jsHeapSizeLimit: 793000000 // 内存大小限制
},

// 哲学问题:我从哪里来?
navigation: {
redirectCount: 0, // 如果有重定向的话,页面通过几次重定向跳转而来
type: 0 // 0 即 TYPE_NAVIGATENEXT 正常进入的页面(非刷新、非重定向等)
// 1 即 TYPE_RELOAD 通过 window.location.reload() 刷新的页面
// 2 即 TYPE_BACK_FORWARD 通过浏览器的前进后退按钮进入的页面(历史记录)
// 255 即 TYPE_UNDEFINED 非以上方式进入的页面
},

timing: {
// 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
navigationStart: 1441112691935,

// 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
unloadEventStart: 0,

// 和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
unloadEventEnd: 0,

// 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
redirectStart: 0,

// 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0
redirectEnd: 0,

// 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
fetchStart: 1441112692155,

// DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupStart: 1441112692155,

// DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupEnd: 1441112692155,

// HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等
// 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
connectStart: 1441112692155,

// HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等
// 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间
// 注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过
connectEnd: 1441112692155,

// HTTPS 连接开始的时间,如果不是安全连接,则值为 0
secureConnectionStart: 0,

// HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存
// 连接错误重连时,这里显示的也是新建立连接的时间
requestStart: 1441112692158,

// HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
responseStart: 1441112692686,

// HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
responseEnd: 1441112692687,

// 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
domLoading: 1441112692690,

// 完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件
// 注意只是 DOM 树解析完成,这时候并没有开始加载网页内的资源
domInteractive: 1441112693093,

// DOM 解析完成后,网页内资源加载开始的时间
// 在 DOMContentLoaded 事件抛出前发生
domContentLoadedEventStart: 1441112693093,

// DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
domContentLoadedEventEnd: 1441112693101,

// DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
domComplete: 1441112693214,

// load 事件发送给文档,也即 load 回调函数开始执行的时间
// 注意如果没有绑定 load 事件,值为 0
loadEventStart: 1441112693214,

// load 事件的回调函数执行完毕的时间
loadEventEnd: 1441112693215

// 字母顺序
// connectEnd: 1441112692155,
// connectStart: 1441112692155,
// domComplete: 1441112693214,
// domContentLoadedEventEnd: 1441112693101,
// domContentLoadedEventStart: 1441112693093,
// domInteractive: 1441112693093,
// domLoading: 1441112692690,
// domainLookupEnd: 1441112692155,
// domainLookupStart: 1441112692155,
// fetchStart: 1441112692155,
// loadEventEnd: 1441112693215,
// loadEventStart: 1441112693214,
// navigationStart: 1441112691935,
// redirectEnd: 0,
// redirectStart: 0,
// requestStart: 1441112692158,
// responseEnd: 1441112692687,
// responseStart: 1441112692686,
// secureConnectionStart: 0,
// unloadEventEnd: 0,
// unloadEventStart: 0
}
};

如何使用

使用 performance.timing 信息简单计算出网页性能数据

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
43
44
45
46
47
48
49
50
51
52
53
54
55
// 计算加载时间
function getPerformanceTiming () {
var performance = window.performance;

if (!performance) {
// 当前浏览器不支持
console.log('你的浏览器不支持 performance 接口');
return;
}

var t = performance.timing;
var times = {};

//【重要】页面加载完成的时间
//【原因】这几乎代表了用户等待页面可用的时间
times.loadPage = t.loadEventEnd - t.navigationStart;

//【重要】解析 DOM 树结构的时间
//【原因】反省下你的 DOM 树嵌套是不是太多了!
times.domReady = t.domComplete - t.responseEnd;

//【重要】重定向的时间
//【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com
times.redirect = t.redirectEnd - t.redirectStart;

//【重要】DNS 查询时间
//【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长?
// 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;

//【重要】读取页面第一个字节的时间
//【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么?
// TTFB 即 Time To First Byte 的意思
// 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte
times.ttfb = t.responseStart - t.navigationStart;

//【重要】内容加载完成的时间
//【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么?
times.request = t.responseEnd - t.requestStart;

//【重要】执行 onload 回调函数的时间
//【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么?
times.loadEvent = t.loadEventEnd - t.loadEventStart;

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

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

// TCP 建立连接完成握手的时间
times.connect = t.connectEnd - t.connectStart;

return times;
}

使用 performance.getEntries() 获取所有资源请求的时间数据

这个函数返回的将是一个数组,包含了页面中所有的 HTTP 请求, HTTP 请求有可能命中本地缓存,所以请求响应的间隔将非常短

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
var entry = {  
// 资源名称,也是资源的绝对路径
name: "http://cdn.alloyteam.com/wp-content/themes/alloyteam/style.css",
// 资源类型
entryType: "resource",
// 谁发起的请求
initiatorType: "link", // link 即 <link> 标签
// script 即 <script>
// redirect 即重定向
// 加载时间
duration: 18.13399999809917,

redirectStart: 0,
redirectEnd: 0,

fetchStart: 424.57699999795295,

domainLookupStart: 0,
domainLookupEnd: 0,

connectStart: 0,
connectEnd: 0,

secureConnectionStart: 0,

requestStart: 0,

responseStart: 0,
responseEnd: 442.7109999960521,

startTime: 424.57699999795295
};

可以像 getPerformanceTiming 获取网页的时间一样,获取某个资源的时间

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
// 计算加载时间
function getEntryTiming (entry) {
var t = entry;
var times = {};

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

// DNS 查询时间
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;

// 内容加载完成的时间
times.request = t.responseEnd - t.requestStart;

// TCP 建立连接完成握手的时间
times.connect = t.connectEnd - t.connectStart;

// 挂载 entry 返回
times.name = entry.name;
times.entryType = entry.entryType;
times.initiatorType = entry.initiatorType;
times.duration = entry.duration;

return times;
}

// test
// var entries = window.performance.getEntries();
// entries.forEach(function (entry) {
// var times = getEntryTiming(entry);
// console.log(times);
// });

##上报数据
通过图片上报

优点

  • 没有跨域问题
  • 通过http code来实现成功确认
  • 在绝大多数客户端关闭时,可以保证请求被发送出去

缺点

  • 只支持GET的方式,数据拼接在URL上,对数据量有限制
  • 关闭页面时,发送请求没有被abort时,会导致页面关闭时间变长
  • 关闭页面时,发送请求可能被abort

通过Ajax上报

优点

  • 可以通过http code 或者 response body 来实现成功确认
  • 支持GET和POST,支持大数据量的发送
  • 支撑多种数据类型

缺点

  • 有跨域问题,需要上报端增加跨域支持
  • 在浏览器关闭时,不能保证请求可以被发送出去,可能被abort

Navigator.sendBeacon()

语法:

1
navigator.sendBeacon(url, data);

参数:

描述:

使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:使它可靠,异步并且不会影响下一页面的加载。此外,代码实际上还要比其他技术简单!该方法发送数据的 HTTP 方法是 POST可以跨域,类似于表单提交数据。它不能指定回调函数

1
2
3
4
5
window.addEventListener('unload', logData, false);

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

兼容性:

WX20200114-124455@2x

简单实现

https://github.com/qinhanwen/performance-api.git

1
2
3
4
const performance = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance;
console.log(performance);
console.log(performance.timing);
console.log(performance.getEntries());

先打印相关信息看一下,可以参照上面的文档

参考资料

http://cf.meitu.com/confluence/pages/viewpage.action?pageId=80614582

http://www.alloyteam.com/2015/09/explore-performance/

https://juejin.im/post/5b5dcfb46fb9a04f8f37afbb