Performance前端性能监控利器

Performance前端性能监控利器

Performance 概要

Performance API用于精确度量、控制、增强浏览器的性能表现。这个API为测量网站性能,提供以前没有办法做到的精度。

比如,为了得到脚本运行的准确耗时,需要一个高精度时间戳。传统的做法是使用Date对象的getTime方法。

这种做法有两个不足之处。首先,getTime方法(以及Date对象的其他方法)都只能精确到毫秒级别(一秒的千分之一),想要得到更小的时间差别就无能为力了;其次,这种写法只能获取代码运行过程中的时间进度,无法知道一些后台事件的时间进度,比如浏览器用了多少时间从服务器加载网页。

为了解决这两个不足之处,ECMAScript 5(W3C标准定义)引入“高精度时间戳”这个API,部署在performance对象上。它的精度可以达到1毫秒的千分之一(1秒的百万分之一),这对于衡量的程序的细微差别,提高程序运行速度很有好处,而且还可以获取后台事件的时间进度。

目前,所有主要浏览器都已经支持performance对象,包括Chrome 20+、Firefox 15+、IE 10+、Opera 15+。兼容性参考

使用 window.performance 提供了一组精确的数据,经过简单的计算就能得出一些网页性能数据。
配合上报一些客户端浏览器的设备类型等数据,就可以实现简单的统计啦!

chrome 输出结果如下图

performance 说明

// 获取 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实际应用

一般来说,我们需要获取到的页面性能参数包括:DNS查询耗时、TCP链接耗时、request请求耗时、解析dom树耗时、白屏时间、domready时间、onload时间等,而这些参数是通过上面的performance.timing各个属性的差值组成的,计算方法如下:

DNS查询耗时 :domainLookupEnd - domainLookupStart
TCP链接耗时 :connectEnd - connectStart
request请求耗时 :responseEnd - responseStart
解析dom树耗时 : domComplete - domInteractive
白屏时间 :responseStart - navigationStart
domready时间 :domContentLoadedEventEnd - navigationStart
onload时间 :loadEventEnd - navigationStart

使用 performance.timing 信息简单计算出网页性能数据参考代码 部分重要时间代码注释参考

// 计算加载时间
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 静态资源加载对象

使用 performance.getEntries() 获取所有资源请求的时间数据,
这个函数返回的将是一个数组,包含了页面中所有的 HTTP 请求,这里拿第一个请求 window.performance.getEntries()[12] 举例。 注意 HTTP 请求有可能命中本地缓存,所以请求响应的间隔将非常短 可以看到,与 performance.timing 对比: 没有与 DOM 相关的属性:

performance.timing已有说明如下

navigationStart

unloadEventStart

unloadEventEnd

domLoading

domInteractive

domContentLoadedEventStart

domContentLoadedEventEnd

domComplete

loadEventStart

loadEventEnd

新增属性

name

entryType

initiatorType

duration

与 window.performance.timing 中包含的属性就不再介绍了:

var entry = {  
initiatorType: "link",
connectEnd: 302.1000000089407,
connectStart: 302.1000000089407,
decodedBodySize: 29400, //在删除任何应用的内容编码之后,从消息正文( message body )的提取(HTTP或缓存)接收的大小(以八位字节为单位)的数字。 浏览器Content-Length
domainLookupEnd: 302.1000000089407,
domainLookupStart: 302.1000000089407,
duration: 2.7000000029802322,
encodedBodySize: 18421, //在删除任何应用的内容编码之前,从payload body的提取(HTTP或高速缓存)接收的大小(以八位字节为单位)的数字。
entryType: "resource", // 资源类型
  // 谁发起的请求
fetchStart: 302.1000000089407,
initiatorType: "link" ,// link 即 <link> 标签
                           // script 即 <script>
                           // redirect 即重定向
// 资源名称,也是资源的绝对路径
name: "https://at.alicdn.com/t/font_1099383_dkenim00q0i.css",
nextHopProtocol: "h2",
redirectEnd: 0,
redirectStart: 0,
requestStart: 303.40000000596046,
responseEnd: 304.80000001192093,
responseStart: 304.20000000298023,
secureConnectionStart: 302.1000000089407,
serverTiming: [],
startTime: 302.1000000089407,
transferSize: 0, //如果资源是从本地缓存中获取的,或者是跨源资源,则此属性返回零。
workerStart: 0,
}

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

// 计算加载时间
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);
// });

performance 其他对象

performance.navigation对象

除了时间信息,performance还可以提供一些用户行为信息,主要都存放在performance.navigation对象上面。

它有两个属性:

(1)performance.navigation.type

该属性返回一个整数值,表示网页的加载来源,可能有以下4种情况:

0:网页通过点击链接、地址栏输入、表单提交、脚本操作等方式加载,相当于常数performance.navigation.TYPE_NAVIGATENEXT。

1:网页通过“重新加载”按钮或者location.reload()方法加载,相当于常数performance.navigation.TYPE_RELOAD。

2:网页通过“前进”或“后退”按钮加载,相当于常数performance.navigation.TYPE_BACK_FORWARD。

255:任何其他来源的加载,相当于常数performance.navigation.TYPE_UNDEFINED。

(2)performance.navigation.redirectCount

该属性表示当前网页经过了多少次重定向跳转。

performance.now()

performance.now() 与 Date.now() 不同的是,返回了以微秒(百万分之一秒,毫秒带小数点)为单位的时间,更加精准。
并且与 Date.now() 会受系统程序执行阻塞的影响不同,performance.now()的时间是以恒定速率递增的,不受系统时间的影响(系统时间可被人为或软件调整)。
注意 Date.now() 输出的是 UNIX 时间,即距离 1970 的时间,而 performance.now() 输出的是相对于 performance.timing.navigationStart(页面初始化) 的时间。
使用 Date.now() 的差值并非绝对精确,因为计算时间时受系统限制(可能阻塞)。但使用 performance.now() 的差值,并不影响我们计算程序执行的精确时间。
performance.now()
16491039
performance.now()
16494068.100000009
performance.now()
16496072.800000012

###参考文档链接

developer.mozilla.org/PerformanceEntry

MDN-Performance

Performance API

使用性能API快速分析web前端性能

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值