你的位置:首页 > Java教程

[Java教程]前端性能——监控起步


前端性能

1.关键点

  分页面、区域、浏览器、性能指标

  页面的性能指标详解:

  白屏时间(first Paint Time)——用户从打开页面开始到页面开始有东西呈现为止

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

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

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

 

  确定统计起点:

  我们需要在用户输入 URL 或者点击链接的时候就开始统计,因为这样才能衡量用户的等待时间。高端浏览器Navigation Timing接口;普通浏览器通过 cookie 记录时间戳的方式来统计,需要注意的是 Cookie 方式只能统计到站内跳转的数据。

  

2.如何统计性能指标的时间

2.1白屏时间

  公式:

  白屏时间=开始渲染时间(首字节时间+HTML下载完成时间)+头部资源加载时间

  

  如何获取:

  chrome 高版本:

  window.chrome.loadTimes().firstPaintTime loadTimes获取的结果

{ connectionInfo: "http/1", finishDocumentLoadTime: 1422412260.278667, finishLoadTime: 1422412261.083637, firstPaintAfterLoadTime: 1422412261.094726, firstPaintTime: 1422412258.085214, navigationType: "Reload", npnNegotiatedProtocol: "unknown", requestTime: 0, startLoadTime: 1422412256.920803, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: false, wasNpnNegotiated: false}

   所以计算公式:

(chrome.loadTimes().firstPaintTime - chrome.loadTimes().startLoadTime)*1000

 

  其他浏览器:

  大部分浏览器没有特定函数,必须想其他办法来监测。仔细观察 WebPagetest 视图分析发现,白屏时间出现在头部外链资源加载完附近,因为浏览器只有加载并解析完头部资源才会真正渲染页面。基于此我们可以通过获取头部资源加载完的时刻来近似统计白屏时间。尽管并不精确,但却考虑了影响白屏的主要因素:首字节时间和头部资源加载时间(HTML下载完成时间非常微小)。

  

  有一个点:mod_36ad799.js等几个js为什么会在hm.js之前下载?html代码如下

  

  这貌似与我们熟知的脚本阻塞解析不符啊,理应是脚本插入hm.js在先,导致DOM树改变,重新绘制DOM树,然后继续往下解析……原因是现在的浏览器对这个过程做了优化:

处理脚本及样式表的顺序(The order of processing scripts and style sheets)

  脚本

  web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的,则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。

  预解析(Speculative parsing)

  Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。

  样式表(Style sheets)

  样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而Chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。

  所以就得到了上面的那个结果

  看看IE的处理

  

   

  回归正题,普通浏览器需要获取两个时间:开始渲染时间头部资源加载时间:

  开始渲染时间:

  需要借助浏览器的navigator timing属性performance;window.performance.timing(Navigation timing性能时间线) 相关属性:

// 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等navigationStart: 1441112691935, // 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0unloadEventStart: 0,unloadEventEnd: 0, // 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0 redirectStart: 0,redirectEnd: 0, ... // 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件domLoading: 1441112692690, ...

  

var timing = performance.timing;var loadingTime = timing .domLoading - timing.navigationStart;//开始渲染时间

   看一下navigator timing浏览器支持情况

  

  对于IE等低版本浏览器是不行的。

  IE8 等低版本浏览器 通过 cookie 记录时间戳的方式来统计,需要注意的是 Cookie 方式只能统计到站内跳转的数据。 首次进入没有好的统计方法。

   

  头部资源加载时间: 

<!DOCTYPE HTML><html>  <head>    <meta charset="UTF-8"/>  <script>   var start_time = +new Date; //测试时间起点,实际统计起点为 DNS 查询  </script>  <!-- 3s 后这个 js 才会返回 -->  <script src="script.php"></script>   <script>   var end_time = +new Date; //时间终点   var headtime = end_time - start_time; //头部资源加载时间     console.log(headtime);  </script>  </head>   <body>     <p>在头部资源加载完之前页面将是白屏</p>  <p>script.php 被模拟设置 3s 后返回,head 底部内嵌 JS 等待前面 js 返回后才执行</p>  <p>script.php 替换成一个执行长时间循环的 js 效果也一样</p>   </body></html>

   这个比较简单,在head的前面计时开始,在head最末尾计时结束,中间的差值就计算为头部资源加载时间。

  所以,最终计算方法:

var firstPaintTime = end_time - performance.timing.navigationStart

  

 2.2首屏时间

  首屏时间的统计比较复杂,因为涉及图片等多种元素及异步渲染等方式。观察加载视图可发现,影响首屏的主要因素的图片的加载。通过统计首屏内图片的加载时间便可以获取首屏渲染完成的时间。统计流程如下:

  首屏位置调用 API 开始统计 -> 绑定首屏内所有图片的 load 事件 -> 页面加载完后判断图片是否在首屏内,找出加载最慢的一张 -> 首屏时间

  这是同步加载情况下的简单统计逻辑,另外需要注意的几点:

  • 页面存在 iframe 的情况下也需要判断加载时间
  • gif 图片在 IE 上可能重复触发 load 事件需排除
  • 异步渲染的情况下应在异步获取数据插入之后再计算首屏
  • css 重要背景图片可以通过 JS 请求图片 url 来统计(浏览器不会重复加载)
  • 没有图片则以统计 JS 执行时间为首屏,即认为文字出现时间
//IE gif重复onload解决var img=new Image(); img.load=function(){ //do something img.load=null;//重新赋值为null } img.src='××.gif';

   

  统计方法1:

  原理:在首屏渲染之前埋上处理逻辑,使用定时器不断的去检测img节点的图片。判断图片是否在首屏和加载完成,找到首屏中加载时间最慢的的图片完成的时间,从而计算出首屏时间。如果首屏有没有图片,如果没图片就用domready时间。

  缺点: 1.浏览器定时器最大精度为55ms 2.背景图片加载没有计算在内 3.不断检测并执行的脚本耗时

//1,获取首屏基线高度//2,计算出基线dom元素之上的所有图片元素//3,所有图片onload之后为首屏显示时间 function getOffsetTop(ele) {  var offsetTop = ele.offsetTop;  if (ele.offsetParent !== null) {   offsetTop += getOffsetTop(ele.offsetParent);  }  return offsetTop; } var firstScreenHeight = win.screen.height; var firstScreenImgs = []; var isFindLastImg = false; var allImgLoaded = false; var t = setInterval(function() {  var i, img;  if (isFindLastImg) {   if (firstScreenImgs.length) {    for (i = 0; i < firstScreenImgs.length; i++) {     img = firstScreenImgs[i];     if (!img.complete) {      allImgLoaded = false;      break;     } else {      allImgLoaded = true;     }    }   } else {    allImgLoaded = true;   }   if (allImgLoaded) {    collect.add({     firstScreenLoaded: startTime - Date.now()    });    clearInterval(t);   }  } else {   var imgs = body.querySelector('img');   for (i = 0; i < imgs.length; i++) {    img = imgs[i];    var imgOffsetTop = getOffsetTop(img);    if (imgOffsetTop > firstScreenHeight) {     isFindLastImg = true;     break;    } else if (imgOffsetTop <= firstScreenHeight      && !img.hasPushed) {     img.hasPushed = 1;     firstScreenImgs.push(img);    }   }  } }, 0); doc.addEventListener('DOMContentLoaded', function() {  var imgs = body.querySelector('img');  if (!imgs.length) {   isFindLastImg = true;  } }); win.addEventListener('load', function() {  allImgLoaded = true;  isFindLastImg = true;  if (t) {   clearInterval(t);  }  collect.log(collect.global); });

View Code

   

  统计方法2:

  原理:对于网页高度小于屏幕的网站来说,只要在页面底部加上脚本打印当前时间即可;或者对于网页高度大于一屏的网页来说,只要在估算接近于一屏幕的元素的位置后,打印一下当前时间。当然这个时间要得把首屏中所有图片的加载时间也算上。

  缺点: 1.需要每个页面手动加入到对应位置 2.背景图片加载没有计算在内

<!DOCTYPE html><html>  <head>    <meta charset="utf-8">    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0">    <script type="text/javascript">      window.logInfo = {};      window.logInfo.openTime = performance.timing.navigationStart;    </script>  </head>  <body>    <div>这是第一屏,这是第一屏</div>    <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">    <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">    <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">    <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">    <div>第一屏结尾,第一屏结尾</div>    <script type="text/javascript">      (function logFirstScreen() {        var images = document.getElementsByTagName('img');        var iLen = images.length;        var curMax = 0;        var inScreenLen = 0;        // 图片的加载回调        function imageBack() {          this.removeEventListener          && this.removeEventListener('load', imageBack, !1);          if (++curMax === inScreenLen) {            // 如果所有在首屏的图片均已加载完成了的话,发送日志            log();          }          }         // 对于所有的位于指定区域的图片,绑定回调事件        for (var s = 0; s < iLen; s++) {          var img = images[s];          var offset = {            top: 0          };          var curImg = img;          while (curImg.offsetParent) {            offset.top += curImg.offsetTop;            curImg = curImg.offsetParent;          }          // 判断图片在不在首屏          if (document.documentElement.clientHeight < offset.top) {            continue;          }          // 图片还没有加载完成的话          if (!img.complete) {            inScreenLen++;            img.addEventListener('load', imageBack, !1);          }        }        // 如果首屏没有图片的话,直接发送日志        if (inScreenLen === 0) {          log();        }        // 发送日志进行统计        function log () {          window.logInfo.firstScreen = +new Date() - window.logInfo.openTime;          console.log('首屏时间:', window.logInfo.firstScreen + 'ms');        }      })();    </script>    <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">    <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">  </body></html>

View Code

  

2.3统计用户可操作

  用户可操作为所有DOM都解析完毕的时间,默认可以统计domready时间,因为通常会在这时候绑定事件操作。对于使用了模块化异步加载的 JS 可以在代码中去主动标记重要 JS 的加载时间,这也是产品指标的统计方式。

  使用jquery中的$(document).ready()即是此意义 window.performance.timing.domInteractive window.performance.timing.domContentLoadedEventStart

  计算公式:

performance.timing.domInteractive - performance.timing.navigationStart

  

2.4总下载时间

  默认可以统计onload时间,这样可以统计同步加载的资源全部加载完的耗时。如果页面中存在很多异步渲染,可以将异步渲染全部完成的时间作为总下载时间。

  计算公式:

performance.timing.loadEventStart- performance.timing.navigationStart

   

2.5统计api相关

片段摘自:美团性能分析框架和性能监控平台,并加入部分其他文字

对于统计脚本,需要满足两个条件:

  • 避免对业务代码的入侵;(独立的脚本)
  • 不影响被测量的页面的性能;(主文档加载完毕之后,再注入统计脚本收集数据,并且尽可能的合并数据请求,减少带宽消耗。)

确定了数据统计脚本的约束条件之后,我们从哪里得到这些数据呢?目前使用的主要途径有:

  • 主文档加载速度,利用 Navigation Timing API 取得;

  

  

  • 静态资源加载速度,利用 Resource Timing API 取得;

  

  • 首次渲染速度,IE 下用 msFirstPaint(window.performance.timing.msFirstPaint) 取得,Chrome 下利用 loadTimes(window.chrome.loadTimes()) 取得,我们的 Chrome 浏览器用户占比超过 70%;
  • 文档生成速度,则是在后端应用内打点来获得;

对于主文档加载速度,我们从宏观到微观的做了这样的分解,从上到下的时间流,右边的时刻标记了每个指标从哪里开始计算到哪里截止,比如,跳转时间 redirect 由 redirectEnd - redirectStart 计算得到,其他的类推:

  

 采集主文档加载速度的具体做法是:

  • 在主文档 load 之前提供可缓存数据的接口,方便在统计脚本载入前就可以准备数据;
  • 在主文档 load 之后注入数据收集脚本,该脚本加载完成之后会处理所有的数据;
  • 利用 Navigation Timing API 收集计算得到上图中的指标;
  • 给所有数据打上页面、地理位置、浏览器等标签,方便更细维度的分析;

对于静态资源的加载速度,我们也做了类似的分解和采集(使用resource timing API):

  

需要特别提示的是,如果你使用 CDN 的话,需要让 CDN 服务商加上 Timing-Allow-Origin 的响应头,才能拿到静态资源的数据。

而对于主文档生成速度,我们则开发了性能统计的 Library,在框架级别集成后端性能的时间指标。

  • High Resolution Timing(高精度计时)

该API规范所定义的JavaScript接口能够提供精确到微秒级的当前时间,并且不会受到系统时钟偏差或调整的影响。对于性能分析来说,精确的测量结果意义重大。

var perf = performance.now();// console output 439985.4570000316

  • Page Visibility (页面可见性)

通过这一规范,网站开发者能够以编程方式确定页面的当前可见状态,从而使网站能够更有效地利用电源与CPU。

当页面获得或失去焦点时,文档对象的visibilitychange事件便会被触发。

document.addEventListener('visibilitychange', function(event){if(document.hidden){// Page currently hidden.}else{// Page currently visible.}});

这一事件对于了解页面的可见状态十分有用,举例来说,用户可能会同时打开多个浏览器标签,而你希望只在用户显示你的网站页面时才进行某些操作(比如播放一段音频文件、或是执行一段JavaScript动画),就可以通过这一事件进行触发。对于移动设备来说,如果用户在某个标签中打开了你的网站,但正在另一个标签中浏览其它内容时,这一特性能够节省该设备的电池消耗。(虽然对于你的网站性能来说意义不大……)

其它部分API功能简介

  • Resource Timing(资源计时)——对单个资源(如图片)的计时,可以对细粒度的用户体验进行检测。
  • Performance Timeline(性能时间线)——以一个统一的接口获取由Navigation Timing、Resourcing Timing和User Timing所收集的性能数据。
  • Battery Status(电池状态)——能够检测当前设备的电池状态,例如是否正在充电、电量等级等等。可以根据当前电量决定是否显示某些内容(例如视频、动画等等),对于移动设备来说非常实用。
  • User Timing(用户计时)——可以对某段代码、函数进行自定义计时,以了解这段代码的具体运行时间,类似于stop watch的作用。
  • Beacon(灯塔)——可以将分析结果或诊断代码发送给服务器,它采用了异步执行的方式,因此不会影响页面中其它代码的运行。对于收集测试结果并进行统计分析来说是一种十分便利的工具。
  • Animation Timing(动画计时) - 通过requestAnimationFrame函数让浏览器精通地控制动画的帧数,能够有效地配合显示器的刷新率,提供更平滑的动画效果,减少对CPU和电池的消耗。
  • Resource Hits(资源提示) - 通过html属性指定资源的预加载,例如在浏览相册时能够预先加载下一张图片,加快翻页的显示速度。
  • Frame Timing(帧计时)——通过一个接口获取与帧相关的性能数据,例如每秒帧数和TTF。该标准目前尚未被支持。
  • Navigation Error Logging(导航错误日志记录)——通过一个接口存储及获取与某个文档的导航相关的错误记录。该标准目前尚未被支持。

 

浏览器支持

下表列举了当前主流浏览器对性能API的支持,其中标注星号的内容并非来自于Web性能工作小组。

规范Internet ExplorerFirefoxChromeSafariOperaiOS SafariAndroid
Navigation Timing931全部8268 (不包括 8.1)4.1
High Resolution Timing1031全部8268 (不包括 8.1)4.4
Page Visibility1031全部7267.14.4
Resource Timing1034全部-26-4.4
Battery Status*-31 (部分支持)38-26--
User Timing10-全部-26-4.4
Beacon-3139-26--
Animation Timing1031全部6.1267.14.4
Resource Hints--仅限Canary版----
Frame Timing-------
Navigation Error Logging-------
WebP*--全部-26-4.1
Picture element and srcset attribute *--38-26--

其它

DZone.com在《Performance & Monitoring 2015》这份白皮书中专门介绍了性能API以及W3C所推荐的新协议、标准及HTML元素,并提供了简单的示例。可以在这里下载完整的白皮书(需要注册)。本文中的示例代码即来自于该白皮书。

如果想了解有关Web性能API的更多内容,可以参考W3C官方文档或这篇博客。

 

2.6 性能优化分为两个阶段来做

  1.使用测试工具自测和优化(工具如ySlow/,线上工具www.webpagetest.org、阿里测、gtmetrix)

  ySlow/ShowSlow:http://www.showslow.com/ 【前端性能监控系统,前端性能指标数据展示,无法实现自动化监控用户真实的应用场景,针对移动端的性能监控,目前由于其本身依赖的工具绝大多数只有PC端,在移动端缺乏相应的数据上报工具(特别是移动端本身复杂的网络环境),所以如果想使用ShowSlow作为前端性能监控平台,需要单独实现数据收集系统,而只是将ShowSlow当作展示系统使用,开源】

  Page Speed: 【基于一系列优化规则对网站进行检测,类似的有Yslow(推荐使用https://gtmetrix.com/来检测网站性能和规则,使用不同的工具检测对比) 】  

  阿里测:基于WebPageTest,网页前端性能测试工具;  

  PhantomJS:自动化监测,模拟Phantom JS 是一个服务器端的 JavaScript API 的 WebKit,基于它可以轻松实现 web 自动化测试。类似的有berserkJS。但是都是服务器模拟测试,不能监控用户真实环境。

 

  webpagetest线上版

  

  

  基于WebPagetest的阿里测(已下线,不举例了,上17测:http://www.17ce.com/)

  综合了pagespeed和ySlow的GTmetrix

 

  2.启用线上监控用户真实情况(前端性能监控平台)

  看几个例子

  透视宝:http://www.toushibao.com/brower.html 【前端性能上报,图表展示,监控用户真实的应用场景,付费】  

  

 

  

  提供了每一个请求的详细信息

  

  

  

 

  可以按选择的字段排序,通过过滤进行类似数据对比,可以查看每一个请求的详细信息,不过按url搜索貌似没有用,如果这个有用的话那就可以对同一个页面做时间的对比。

  

  

  优点:

  1.柱状时间线排列

  2.多个指标同时展示,便于比较

  缺点:

  1.缺少部分关键时间(白屏时间=首字节时间+HTML下载完成时间+头部资源加载时间)

  2.性能没有按地区分类,参考价值大大减少

  3.免费版本只存储3天

 

  Browser Insight:http://www.oneapm.com/bi/feature.html 【前端性能上报,图表展示,监控用户真实的应用场景,付费】

  

 

  

  

  

  

  

  要查看某次访问的详情需要在云快照中拍照,模拟访问

  

  

   优点:

  1.指标齐全

  2.慢加载追踪所有资源加载情况

  缺点:

  1.四个性能指标没有按地区分类的数据,参考价值大大减少

   

  mmtrix(性能魔方):http://www.mmtrix.com/

  先看评测

  http://www.7k7k.com WEB评测实例:统计数据不错

  

 

  

  真实用户性能监控:

  

  

  

  

  优点:

  1.支持不同地域的四个关键性能指标的展示

  2.支持展示不同区间的数据比例

  缺点:

  1.不支持https协议

 

  看一下国外的性能监控网站 

  先看newrelic(rpm.newrelic.com),注册需要自己使用外网代理

  

  和OneAPM很像,前端性能指标不全。用来监控ajax请求, js报错等还不错,但是满足不了我的需求。

 

  appdynamics(www.appdynamics.com)

  注册居然找不到中国

  

  随便选了一个canmroon

  

   和透视宝很像。免费版本保存时间更少,只有24小时。

 

   总的来说,mmtrix和OneAPM指标更全一些。还没有研究他们的监控代码,不知道监控的指标正确与否。公司的性能这块也刚起步,离优秀还有很大一段距离,就写到这里了,希望对研究性能刚起步的童鞋有点作用,任务还很重,加油。

 

  如果觉得本文不错,请点击右下方【推荐】!