“If you cannot measure it, you cannot improve it” ———— William Thomson 如果 Google 延迟 400ms,则搜索量下降 0.59%;Bing 延迟 2s,收入下降 4.3%;Yahoo 延迟 400ms,流量下降 5-9%;Mozilla 页面打开减少 2.2s,下载量提升 15.4%;Netflix 开启 Gzip,性能提升 13.25% 带宽减少50%。 性能影响了用户体验 加载的延迟、操作的卡顿等都会影响用户的使用体验。尤其是移动端,用户对页面响应延迟和连接中断的容忍度很低。想象一下你拿着手机打开一个网页想看到某个信息却加载半天的心情,你很可能选择直接离开换一个网页。谷歌也将页面加载速度作为 SEO 的一个权重。 性能会伴随产品的迭代而有所衰减 特别在移动端,网络一直是一个很大的瓶颈,而页面却越来越大,功能越来越复杂。并没有简单的几条黄金规则就可以搞定性能优化工作,我们需要一套性能监控系统持续监控、评估、预警页面性能状况、发现瓶颈,指导优化工作的进行。 监控用户的痛点 线上监控哪些指标呢?如何更好地反映用户感知? 根据用户的痛点,将浏览器加载过程抽取出四个关键指标,即白屏时间、首屏时间、用户可操作、总下载时间。 1. 确定统计起点 在用户输入 URL 或者点击链接的时候就开始统计,因为这样才能衡量用户的等待时间。如果你的用户高端浏览器占比很高,那么可以直接使用Navigation Timing接口来获取统计起点以及加载过程中的各个阶段耗时。 2. 统计白屏时间 用户从打开页面开始到页面开始有东西呈现为止,这过程中占用的时间就是白屏时间,也叫做首次渲染时间,chrome 高版本有 firstPaintTime 接口来获取这个耗时。 白屏时间出现在头部外链资源加载完附近,因为浏览器只有加载并解析完头部资源才会真正渲染页面。 3. 统计首屏时间 用户浏览器首屏内所有内容都呈现出来所花费的时间就是首屏时间。 4. 统计用户可操作和总下载 用户可操作默认可以统计domready时间,因为通常会在这时候绑定事件操作。总下载时间默认可以统计onload时间,这样可以统计同步加载的资源全部加载完的耗时。 网络耗时统计 网络耗时数据可以借助 Navigation Timing 接口获取,通过此接口可以轻松获取 DNS、TCP、首字节、html 传输等耗时,Navigation Timing 的接口示意图如下所示: 在 Chrome 浏览器控制台中执行 window.performance 让数据会说话 // 获取 performance 数据 var performance = { // memory 是非标准属性,只在 Chrome 有 memory: { usedJSHeapSize: 16100000, // JS 对象占用的内存,一定小于 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 的时间戳 navigationStart: 1441112691935, // 前一个网页 unload 的时间戳 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 } }; 使用 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 树结构的时间 times.domReady = t.domComplete - t.responseEnd; //【重要】重定向的时间 times.redirect = t.redirectEnd - t.redirectStart; //【重要】DNS 查询时间 times.lookupDomain = t.domainLookupEnd - t.domainLookupStart; //【重要】读取页面第一个字节的时间 times.ttfb = t.responseStart - t.navigationStart; //【重要】内容加载完成的时间 times.request = t.responseEnd - t.requestStart; //【重要】执行 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; } Reference:http://www.alloyteam.com/2015/09/explore-performance/ 转载请并标注: “本文转载自 linkedkeeper.com ” ©著作权归作者所有 |