前端数据监控到底在监控什么?

前端数据监控通常分为性能数据监控和线上异常监控。本文对这两块数据的监控原理和方法进行整理说明。php

性能数据

统计方案

  • 代码监控
    • 将监控代码注入到页面中,手动计算时间差或者使用浏览器API进行数据统计。
  • 工具监控
    • 不将统计代码注入到页面中,通常借助虚拟机对页面进行性能数据分析。
类型 优势 缺点 示例
非侵入式 指标齐全、客户端主动监测、竞品监控 没法知道性能影响用户数、采样少容易失真、没法监控复杂应用与细分功能 Pagespeed、PhantomJS、UAQ
侵入式 真实海量用户数据、能监控复杂应用与业务功能、用户点击与区域渲染 需插入脚本统计、网络指标不全、没法监控竞品 DP 、Google 统计

在进行性能数据监控以前,先要明确页面从用户开始访问到页面加载完成经历的时间阶段。css

时间阶段

按触发顺序排列全部属性:(更详细标准的解释请参看:W3C Editor's Draft)html

  • navigationStart:在同一个浏览器上下文中,前一个网页(与当前页面不必定同域)unload 的时间戳,若是无前一个网页 unload ,则与 fetchStart 值相等前端

  • unloadEventStart:前一个网页(与当前页面同域)unload 的时间戳,若是无前一个网页 unload 或者前一个网页与当前页面不一样域,则值为 0git

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

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

  • redirectEnd:最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内的重定向才算,不然值为 0chrome

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

  • domainLookupStart:DNS 域名查询开始的时间,若是使用了本地缓存(即无 DNS 查询)或持久链接,则与 fetchStart 值相等跨域

  • domainLookupEnd:DNS 域名查询完成的时间,若是使用了本地缓存(即无 DNS 查询)或持久链接,则与 fetchStart 值相等

  • connectStart:HTTP(TCP) 开始创建链接的时间,若是是持久链接,则与 fetchStart 值相等,若是在传输层发生了错误且从新创建链接,则这里显示的是新创建的链接开始的时间

  • connectEnd:HTTP(TCP) 完成创建链接的时间(完成握手),若是是持久链接,则与 fetchStart 值相等,若是在传输层发生了错误且从新创建链接,则这里显示的是新创建的链接完成的时间

    注意:这里握手结束,包括安全链接创建完成、SOCKS 受权经过

  • secureConnectionStart:HTTPS 链接开始的时间,若是不是安全链接,则值为 0

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

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

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

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

  • domInteractive:完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件

    注意:只是 DOM 树解析完成,这时候并无开始加载网页内的资源

  • domContentLoadedEventStart:DOM 解析完成后,网页内资源加载开始的时间,文档发生 DOMContentLoaded事件的时间

  • domContentLoadedEventEnd:DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕),文档的DOMContentLoaded 事件的结束时间

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

  • loadEventStart:load 事件发送给文档,也即 load 回调函数开始执行的时间,若是没有绑定 load 事件,值为 0

  • loadEventEnd:load 事件的回调函数执行完毕的时间,若是没有绑定 load 事件,值为 0

DOMContentLoaded 和 load 事件的区别,详见 DOMContentLoaded与load的区别

统计方法

Navigation Timing API

能够直接使用Navigation Timing接口来获取统计起点以及加载过程当中的各个阶段耗时。window.performance 是W3C性能小组引入的新的API,目前IE9以上的浏览器都支持。

经常使用计算:

  • DNS查询耗时 :domainLookupEnd - domainLookupStart
  • TCP连接耗时 :connectEnd - connectStart
  • request请求耗时 :responseEnd - responseStart
  • 解析dom树耗时 : domComplete - domInteractive 白屏时间 :responseStart - navigationStart
  • domready时间(用户可操做时间节点) :domContentLoadedEventEnd - navigationStart
  • onload时间(总下载时间) :loadEventEnd - navigationStart

Resource timing API

Resource timing API是用来统计静态资源相关的时间信息,详细的内容请参考W3C Resource timing。这里咱们只介绍performance.getEntries方法

指标及计算方法

指标

  • 整体指标
    • 到 DOM 可交互耗时timing.domComplete - timing.navigationStart
    • 总耗时timing.loadEventEnd - timing.navigationStart 到 DNS 查询结束耗时timing.domainLookupEnd - timing.navigationStart
    • 到请求结束耗时timing.responseEnd - timing.navigationStart
    • 首次渲染耗时timing.msFirstPaint
  • 阶段指标(按顺序)
    • 重定向时间timing.redirectEnd - timing.redirectStart
    • unload 事件时间timing.unloadEventEnd - timing.unloadEventStart
    • appcache 时间timing.domainLookupStart - timing.fetchStart
    • DNS 查询时间timing.domainLookupEnd - timing.domainLookupStart
    • 链接时间timing.connectEnd - timing.connectStart
    • 请求时间timing.responseEnd - timing.requestStart
    • 请求到 DOM 可交互时间(包含解析HTML,非defer的script和css的时间)timing.domInteractive - timing.responseEnd
    • DOM 可交互到 DOMReady 时间(包含处理defer的script的时间)timing.domComplete - * timing.domInteractive
    • load 事件时间timing.loadEventEnd - timing.loadEventStart

详见源码:timing

关键指标

  • 白屏时间(first paint time)- 用户从打开页面到页面开始有内容呈现为止
  • 首屏时间 - 用户从打开页面到首屏内全部内容都呈现出来所花费的时间
  • 用户可操做时间(dom interactive) - 用户从打开页面到能够进行正常点击、输入等操做的时间
  • 总下载时间 - 用户从打开页面到页面全部资源都加载完毕并呈现出来所化的时间

肯定统计起点

咱们须要在用户输入 URL 或者点击连接的时候就开始统计,由于这样才能衡量用户的等待时间。若是你的用户高端浏览器占比很高,那么能够直接使用Navigation Timing接口来获取统计起点以及加载过程当中的各个阶段耗时。

白屏时间

白屏时间是用户首次看到内容的时间,也叫作首次渲染时间,chrome 高版本有 firstPaintTime 接口来获取这个耗时,但大部分浏览器并不支持,必须想其余办法来监测。仔细观察 WebPagetest 视图分析发现,白屏时间出如今头部外链资源加载完附近,由于浏览器只有加载并解析完头部资源才会真正渲染页面。基于此咱们能够经过获取头部资源加载完的时刻来近似统计白屏时间。尽管并不精确,但却考虑了影响白屏的主要因素:首字节时间和头部资源加载时间。

如何统计头部资源加载呢?咱们发现头部内嵌的 JS 一般需等待前面的 JS\CSS 加载完才会执行,是否是能够在浏览器 head 内底部加一句 JS 统计头部资源加载结束点呢?能够经过一个简单的示例进行测试:

<!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>
复制代码

经测试发现,统计的头部加载时间正好跟头部资源下载时间相近,并且换成一个执行时间很长的 JS 也会等到 JS 执行完才统计。说明此方法是可行的(具体缘由可查看浏览器渲染原理及 JS 单线程相关介绍)。

除了上述方法,还能够采用 navigation timing api 来获取白屏时间。该方法的缺点是浏览器兼容性较差,在 safari 等浏览器中没法使用。

var firstPaint = 0;

  // Chrome
  if (window.chrome && window.chrome.loadTimes) {
      // Convert to ms
      firstPaint = window.chrome.loadTimes().firstPaintTime * 1000;
      api.firstPaintTime = firstPaint - timing.navigationStart;
  }
  // IE
  else if (typeof timing.msFirstPaint === 'number') {
      firstPaint = timing.msFirstPaint;
      api.firstPaintTime = firstPaint - timing.navigationStart;
  }
  // Firefox
  // This will use the first times after MozAfterPaint fires
  //else if (timing.navigationStart && typeof InstallTrigger !== 'undefined') {
  //    api.firstPaint = timing.navigationStart;
  //    api.firstPaintTime = mozFirstPaintTime - timing.navigationStart;
  //}

复制代码

首屏时间

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

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

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

  • 页面存在 iframe 的状况下也须要判断加载时间
  • gif 图片在 IE 上可能重复触发 load 事件需排除
  • 异步渲染的状况下应在异步获取数据插入以后再计算首屏
  • css 重要背景图片能够经过 JS 请求图片 url 来统计(浏览器不会重复加载)
  • 没有图片则以统计 JS 执行时间为首屏,即认为文字出现时间

统计用户可操做

用户可操做默承认以统计domready时间,由于一般会在这时候绑定事件操做。对于使用了模块化异步加载的 JS 能够在代码中去主动标记重要 JS 的加载时间,这也是产品指标的统计方式。

performance.timing.domInteractive - performance.timing.navigationStart
复制代码

总下载

总下载时间默承认以统计onload时间,这样能够统计同步加载的资源所有加载完的耗时。若是页面中存在不少异步渲染,能够将异步渲染所有完成的时间做为总下载时间。

performance.timing.loadEventStart- performance.timing.navigationStart
复制代码

线上异常

接口异常监控

方法很简单,在接口出错的状况下主动打点上报错误。

JS 代码异常监控

  • try catch 捕获

这种方案要求开发人员在编写代码的时候,在预估有异常发生的代码段使用try...catch,在发生异常时将异常信息发送给接口:

try{
//可能发生异常的代码段
}catch(e){
//将异常信息发送服务端
}
复制代码
  • window.onerror捕获

这种方式不须要开发人员在代码中书写大量的try...catch,经过给window添加onerror监听,在js发生异常的时候即可以捕获到错误信息,语法异常和运行异常都可被捕获到。可是window.onerror这个监听必须放在全部js文件以前才能够保证可以捕获到全部的异常信息。

  • 跨域JS文件异常的捕获

目前能够说基本上全部的web产品对于js/css/image等静态资源都在服务端设置了Access-Control-Allow-Origin: *的响应头,也就是容许跨域请求。在这个环境下,只要咱们在请求跨域资源的script标签上添加一个crossorigin属性便可:

<script src="http://static.toutiao.com/test.js" crossorigin></script>
复制代码

这样的话,异域的test.js文件中发生异常时即可以被当前域的onerror监听捕获到详细的异常信息。

参考资料

  1. 前端性能 -- 监控起步
  2. Performance — 前端性能监控利器
  3. 前端性能监控方案window.performance 调研(转)
  4. 研究首屏时间?你先要知道这几点细节
  5. 初探 performance – 监控网页与程序性能
  6. 7 天打造前端性能监控系统
  7. DOMContentLoaded与load的区别
相关文章
相关标签/搜索