前端监控之性能与异常
作者:京东零售 李菲菲
1 前言
现有的大部分监控方案都是针对服务端的,而针对前端的监控很少,诸如线上页面的白屏时间是多少、静态资源的加载情况如何、接口请求耗时好久、什么时候挂掉了、为什么挂掉,这些都不清楚。
同时,在产品推广过程中,经常需要统计页面的使用情况及用户行为,从而可以从运营和产品的角度去了解用户群体,进而迭代升级产品,使其更加贴近用户,为业务的扩展提供更多可能性。
因而,我们需要一个前端的页面监控系统,持续监控和预警页面性能的状况,并且在发现瓶颈时用于指导优化工作。
2 前端监控目标
前端监控主要包含两大块:性能监控及异常监控
- 保证稳定性(异常监控)
错误监控包括 JavaScript 代码错误,Promsie 错误,接口(XHR,fetch)错误,资源加载错误(script,link等)等,这些错误大多会导致页面功能异常甚至白屏。 - 提升用户体验(性能监控)
性能监控包括页面的加载时间,接口响应时间等,侧面反应了用户体验的好坏。
3 性能监控
3.1 简单描述页面加载
简单看一下,从输入url到页面加载完成的过程如下:
首先需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接,随后我们向服务端抛出 HTTP 请求,服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就可以开始走一个渲染的流程。渲染完毕,页面便呈现给了用户。
我们可以将这个过程分为如下的过程片段:
- DNS 解析
- TCP 连接
- HTTP 请求抛出
- 服务端处理请求,HTTP 响应返回
- 浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户
3.2 从开发者角度,看页面加载各阶段
从输入url到用户可以使用页面的全过程时间统计,会返回一个PerformanceTiming对象,单位均为毫秒。
关于performace,已经在《从前端角度浅谈性能》中进行过介绍,,下面再强调一下:
各阶段的性能耗时可以通过API:window.performance来获取,对应的具体方法有:performance.timing、performance.getEntriesByType(‘resource’)、performance.navigation等。
如上,开发者可以通过performance中各阶段的时间戳,分别获取到 页面各阶段的性能指标,具体的个静态资源的加载耗时、及 页面是否重定向和重定向耗时。
按触发顺序排列所有属性:
- navigationStart:在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
- redirectStart:第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
- unloadEventStart:前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
- redirectEnd:最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内的重定向才算,否则值为 0
- unloadEventEnd:和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
- fetchStart:浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
- domainLookupStart:DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
- domainLookupEnd:DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
- connectStart:HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
- secureConnectionStart:HTTPS 连接开始的时间,如果不是安全连接,则值为 0
- connectEnd:HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间
- requestStart:HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存,连接错误重连时,这里显示的也是新建立连接的时间
- responseStart:HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
- responseEnd:HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
- domLoading:开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
- domInteractive:完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件
- domContentLoadedEventStart:DOM 解析完成后,网页内资源加载开始的时间,文档发生 DOMContentLoaded事件的时间
- domContentLoadedEventEnd:DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕),文档的DOMContentLoaded 事件的结束时间
- domComplete:DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
- loadEventStart:load 事件发送给文档,也即 load 回调函数开始执行的时间,如果没有绑定 load 事件,值为 0
- loadEventEnd:load 事件的回调函数执行完毕的时间,如果没有绑定 load 事件,值为 0
3.3 各阶段性能的计算(自定义)
1. `const { timing, navigation } = window.performance` 1. `const loadPageInfo = {};` 1. `` 1. `// 页面加载类型,区分第一次load还是reload, 0初次加载、1重加载` 1. `loadPageInfo.loadType = navigation.type;` 1. `` 1. `// 页面加载完成的时间 - 几乎代表了用户等待页面白屏的时间` 1. `loadPageInfo.loadPage = timing.loadEventEnd - timing.navigationStart;` 1. `` 1. `// 重定向的时间` 1. `loadPageInfo.redirect = timing.redirectEnd - timing.redirectStart;` 1. `` 1. `// 卸载页面的时间` 1. `loadPageInfo.unloadEvent = timing.unloadEventEnd - timing.unloadEventStart;` 1. `` 1. `// 查询 DNS 本地缓存的时间` 1. `loadPageInfo.appCache = timing.domainLookupStart - timing.fetchStart;` 1. `` 1. `// 【重要】DNS 查询时间` 1. `// 页面内是不是使用了太多不同的域名,导致域名查询的时间太长?推荐 DNS 预加载。` 1. `// 可使用 HTML5 Prefetch 预查询 DNS` 1. `loadPageInfo.lookupDomain = timing.domainLookupEnd - timing.domainLookupStart;` 1. `` 1. `// HTTP(TCP)建立连接完成握手的时间` 1. `loadPageInfo.connect = timing.connectEnd - timing.connectStart;` 1. `` 1. `// 【重要】HTTP请求及获取 文档内容的时间` 1. `loadPageInfo.request = timing.responseEnd - timing.responseStart;` 1. `` 1. `// 【重要】前一个页面 unload 到 HTTP获取到 页面第一个字节的时间` 1. `// 【原因】这可以理解为用户拿到你的资源占用的时间,推荐 加异地机房,加 CDN 处理,加宽带,加 CPU 运算速度` 1. `// TTFB 即 Time To First Byte` 1. `loadPageInfo.ttfb = timing.responseStart - timing.navigationStart;` 1. `` 1. `// 解析 DOM 树结构的时间` 1. `loadPageInfo.domReady = timing.domComplete - timing.responseEnd;` 1. `` 1. `// 【重要】执行 onload 回调函数的时间` 1. `// 【原因】是否太多不必要的操作都放在 onload 回调函数里执行了,推荐 延迟加载、按需加载的策略` 1. `loadPageInfo.loadEvent = timing.loadEventEnd - timing.loadE`
4 异常监控
前端需要监控的错误主要有两类:
- Javascript错误(js错误、promise错误)
- 监听error错误(资源加载错误)
4.1 console.error
1. `// 重写console.error,可以捕获更全面的报错信息` 1. `var oldError = console.error;` 1. `` 1. `` 1. `console.error = function(tempErrorMsg){` 1. `var errorMsg = ( arguments[0] && arguments[0].message ) || tempErrorMsg;` 1. `var lineNumber = 0;` 1. `var columnNumber = 0;` 1. `var errorStack = arguments[0] && arguments[0].stack;` 1. `` 1. `` 1. `if( !errorStack ){` 1. `saveJSError( 'console_error', errorMsg, '', lineNumber, columnNumber, 'CustomizeError: ' + errorMsg );` 1. `}else{` 1. `saveJSError( 'console_error', errorMsg, '', lineNumber, columnNumber, errorStack );` 1. `}` 1. `` 1. `` 1. `return oldError.apply( console, arguments );`
4.2 error事件
通过对error事件的监听,可以捕捉到 js语法 及 资源加载 的错误。根据 event.target.src / href 来判断是否为资源加载错误。
1. `window.addEventListener( 'error', function(e){` 1. `var errorMsg = e.error && e.error.message,` 1. `errorStack = e.error && e.error.stack,` 1. `pageUrl = e.filename,` 1. `lineNumber = e.lineno,` 1. `columnNumber = e.colno;` 1. `` 1. `` 1. `saveJSError( 'on_error', errorMsg, pageUrl, lineNumber, columnNumber, errorStack );` 1. `} );`
4.3 Promise
`// 捕获未处理的Promise错误` `window.onunhandledrejection = function(e){` `var errorMsg = '';` `var errorStack = '';` `if( typeof e.reason === 'object' ){` `errorMsg = e.reason.message;` `errorStack = e.reason.stack;` `}else{` `errorMsg = e.reason;` `errorStack = '';` `}` `saveJSError( 'on_error', errorMsg, '', 0, 0, 'UncaughtInPromiseError: ' + errorStack );` `}`
5 总结
以上,通过简单的js代码,即可实现对页面性能与异常的监控与数据上报,后续还需要相应具体的平台汇总,及相应的业务所需数据(如PV、UV等)的计算,才能真正实现对产品的页面数据呈现,用于业务扩展及宣导。
6 后续
上述代码,实现了对页面性能及异常的监控,但其实前端的监控还包括了请求接口的监控与埋点的实现,后续将陆续推出,敬请期待。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
一文详解 Netty 组件
作者:京东物流 张弓言 一、背景 Netty 是一款优秀的高性能网络框架,内部通过 NIO 的方式来处理网络请求,在高负载下也能可靠和高效地处理 I/O 操作 作为较底层的网络通信框架,其被广泛应用在各种中间件的开发中,比如 RPC框架、MQ、Elasticsearch等,这些中间件框架的底层网络通信模块大都利用到了 Netty 强大的网络抽象 下面这篇文章将主要对 Netty 中的各个组件进行分析,并在介绍完了各个组件之后,通过 JSF 这个 RPC 框架为例来分析 Netty 的使用,希望让大家对 Netty 能有一个清晰的了解 二、Netty Server 通过 Netty 来构建一个简易服务端是比较简单的,代码如下: public class NettyServer { public static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class); public static void main(String[] args) { ServerBootstrap serverBootstra...
- 下一篇
测试角色在项目各阶段的项目管理tips
作者:京东物流 宋雪薇 1 前言 项目管理是一个繁杂的过程,每个阶段需要涉及到不同人员、资源的协调配合。每个角色都有自己的定位和任务,为了紧密配合项目经理或无分配项目经理运行项目的场景下确保项目成员共同达成项目目标,不同的角色掌握相应的项目管理意识就尤为重要。 那么,测试角色作为项目交付的质量把控者,具备相应的项目管理意识在项目的高质量、高效率交付目标上有着重要作用,如前置识别质量风险、进度风险等。本文旨在梳理、谈论测试角色在项目各阶段如何评估测试范围及风险、前置暴露问题以及推进测试进度等项目管理事项,高效协作及交付测试角色产物,最终与项目各方共同推进达到高质量、高效率交付的目标。 2 现状及思考 在现有敏捷迭代快速交付模式下,针对某一需求/项目会拆分至各个团队,各个团队节奏及交付目标不完全一致,且无项目经理角色跟踪推进的情况下,存在后置与协作团队沟通确认事项,如:未拉齐依赖方排期、前期未识别出改动系统、需求/设计变更未及时同步相关方、无设计方案沟通导致提测内容不满足提测标准,等均可影响交付节奏。那么作为测试角色的我们可以做哪些事情? 核心主旨:高效沟通协作,提前思考后续阶段较容易影响...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Hadoop3单机部署,实现最简伪集群
- CentOS6,7,8上安装Nginx,支持https2.0的开启