前端数据监控一般分为性能数据监控和线上异常监控.本文对这两块数据的监控原理和方法进行整理说明.
性能数据
统计方案
代码监控
将监控代码注入到页面中,手动计算时间差或者使用浏览器 API 进行数据统计.
工具监控
不将统计代码注入到页面中,一般借助虚拟机对页面进行性能数据分析.
类型 优点 缺点 示例
非侵入式 指标齐全,客户端主动监测,竞品监控 无法知道性能影响用户数,采样少容易失真,无法监控复杂应用与细分功能 Pagespeed,PhantomJS,UAQ
侵入式 真实海量用户数据,能监控复杂应用与业务功能,用户点击与区域渲染 需插入脚本统计,网络指标不全,无法监控竞品 DP ,Google 统计
在进行性能数据监控之前,先要明确页面从用户开始访问到页面加载完成经历的时间阶段.
时间阶段
按触发顺序排列所有属性:(更详细标准的解释请参看: W3C Editor's Draft )
navigationStart: 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
unloadEventStart: 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
unloadEventEnd: 和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
redirectStart: 第一个 HTTP 重定向发生时的时间.有跳转且是同域名内的重定向才算,否则值为 0
redirectEnd: 最后一个 HTTP 重定向完成时的时间.有跳转且是同域名内的重定向才算,否则值为 0
fetchStart: 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
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 统计头部资源加载结束点呢?可以通过一个简单的示例进行测试:
经测试发现,统计的头部加载时间正好跟头部资源下载时间相近,而且换成一个执行时间很长的 JS 也会等到 JS 执行完才统计.说明此方法是可行的 (具体原因可查看浏览器渲染原理及 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>
除了上述方法,还可以采用 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,在发生异常时将异常信息发送给接口:
window.onerror 捕获
try {
//可能发生异常的代码段
} catch(e) {
//将异常信息发送服务端
}
这种方式不需要开发人员在代码中书写大量的 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 监听捕获到详细的异常信息.
参考资料
前端性能 -- 监控起步
Performance — 前端性能监控利器
前端性能监控方案 window.performance 调研 (转)
研究首屏时间?你先要知道这几点细节
初探 performance - 监控网页与程序性能
7 天打造前端性能监控系统
DOMContentLoaded 与 load 的区别
来源: https://juejin.im/post/5a5ba6616fb9a01ca7136a8f