前端性能监控的基本介绍这是我之前在我们团队中做的一次分享,很多地方写的比较简单,就是抛砖引玉,大致讲解下前端性能监控的基本思路。
1. 性能监控的目的、意义 #
服务上线,才是万里长征的第一步,我们还要根据收集到性能收据和业务数据,进行后续的调整和优化。
web 的性能一定程度上影响了用户留存率,用户各种情况不确定,诸如网络情况,机型,浏览器版本等,或者项目本身的迭代更新。不可能每一项在本地测试过程中,都能覆盖的到。而且,随着版本的迭代,有的指标会发生变化。若没有监控,线上的一切情况对我们来说就是一个黑盒。
因此这里需要一个性能监控系统,监控和预警页面的性能,并且能在发现瓶颈的时候来进行优化。
我们可转化为三个方面来看:响应速度、页面稳定性、外部服务调用
- 关注性能指标,提升用户体验:老说性能优化,那哪个性能有问题,得先知道;
- 及早发现错误:内部测试时,受限于用户规模小、机型少等原因,无法覆盖到所有场景;通过该监控,可以及早发现错误并修正;不用等待用户的主动反馈(能主动反馈问题的用户都是忠实用户);
- 关注第三方系统指标:比如调用的后端接口、其他第三方接口等,可以排查出是前端问题、还是后端问题等;
2. 监控什么指标? #
我们在上面分成了三部分来监控,每项细分的话,主要有:
2.1 性能指标 #
- 各种资源的加载时间,包括 html, js, css, img 等资源;
- 首页、首屏、整页的渲染耗时(服务端渲染和客户端渲染);
- 慢日志(加载在 10s 以上的资源);
比如首屏渲染耗时的计算,根据渲染方式的不同,计算方法也不一样。若采用服务端渲染的,只需要在恰当的位置打点就行。但若采用的是前端渲染,则计算方法就复杂的多。
- 在代码组件中上报,比如 useEffect() 中,这对代码的侵入性比较大;
- IntersectionObserver + MutationObserver ,监听节点的变化,判断出现在首屏中的节点,待所有的节点都不再变动,则认为首屏渲染完毕(当然,这里也要考虑到图片等资源的耗时);
2.2 各种错误 #
- 未捕获的错误,如从未定义的变量中获取某属性,未 catch 的 Promise 异常等;
- 开发者主动抛出的错误,如 console.error;
- 静态资源(如图片等)加载异常;
2.3 第三方系统的数据 #
- 请求接口的成功率(如 200、4xx、5xx 等);
- 接口的耗时、code 码等;
2.4 公共指标 #
- ua;
- 当前页的 url;
- 设备尺寸、分辨率、设备像素比等;
- 用户标识(openid、uid 等)、设备标识;
3. 怎么监控这些指标 #
3.1 性能指标 #
性能指标可以通过 performance 来获取。
html 页面的数据,如 ttfb,白屏时间等,可以通过 performance.timing 来获取。
其他的如 js\css\img 等资源,可以通过 performance.getEntries() 来获取。
3.2 各种错误 #
被动捕获的全局错误。
window.addEventListenter("error", (event) => {
upload("error", event);
});
// 未捕获的 reject 错误
window.addEventListenter("unhandledRejection", (event) => {
upload("unhandledRejection", event);
});
主动输出的错误:
const originalError = console.error;
console.error = (...rest) => {
originalError(...rest);
upload("console.error", ...rest);
};
注意,使用throw
抛出的错误,会被 onerror 捕获。
3.3 监控接口的数据 #
一般是劫持发送请求的方法,然后埋上自己的监控代码。
以 XMLHttpRequest 为例,我们要监听的是他的方法,而不是这个类。
const originalSend = XMLHttpRequest.prototype.send;
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
originalOpen.call(this, method, url, ...rest);
};
XMLHttpRequest.prototype.send = function (data) {
this.startTime = Date.now();
const { onloadend, ontimeout } = this;
this.onloadend = function () {
onloadend();
};
this.ontimeout = function () {
ontimeout();
};
originSend.call(this, data);
};
fetch 方法同理:
const originalFetch = window.fetch;
window.fetch = function (...rest) {
const startTime = Date.now();
return originalFetch(...rest).then((response) => {
upload("fetch", response);
return response;
});
};
4. 上报哪些数据 #
我们依照的原则是:
- 在不侵犯用户隐私的前提下,尽可能多维度的收集信息,来更好地分析和定位问题;
- 尽量不漏报数据、不死循环(如自己上报自己的错误、劫持方法没写好);
- 通用,与前端使用的框架无关;并且,减少开发者的使用负担(能主动收集的,就不麻烦开发者);
给到开发者的都是一些可选的配置,如:
- 用户的标识:每个应用对用户的标识是不一样的,可以让开发者自行定义用户的标识,如有的是 openid,有的是 uid,手 Q 里是 uin 等;
- 采样率:有的流量特别大的应用,可以设置采样率(0.01~1),避免上报的数据量过多;
- 是否是 spa 应用:
5. 如何上报这些数据 #
有三种方式,主要是前 2 种。
优点 | 缺点 | |
---|---|---|
创建 img 图片 | 方式简单,可跨域,兼容性好 | 数据量有限制 |
xhr 的 post 请求 | 数据大小没有限制 | 需特殊处理跨域限制 |
navigator.sendBeacon | 页面卸载前上报数据避免丢失 | 兼容性一般 |
其实要说 sendBeacon() 的兼容性,目前也算还可以,大部分主流浏览器都是满足的:sendbeacon 的兼容性。
有些 sdk 为了避免多次高频的数据上报,也会先在本地积攒一定的数量或时间后,再统一进行上报。
6. 上报之后 #
数据上报之后,主要是进行 3 个过程:
- 数据清洗;
- 敏捷分析;
- 展示和告警;
6.1 数据清洗 #
对一些不规范或缺失的数据,进行补全或剔除。
6.3 展示和告警 #
从原始的数据中分析出页面的性能和错误率后,需要在某一个平台上进行展示和过滤,方便我们前端开发者可以定位问题。
同时,为了及早发现错误,也需要配置相应的告警机制。告警策略也分很多种,如:
- 环比:单位时间内数据曲线比前一个周期有激增或急降(大部分都设置这个);
- 同比:与昨日同时间段内的数据波动;
- 按照不同的字段,告警的单位也是不一样的,如 pv 数据、错误数据等一般是按照百分比;首页耗时等是按照耗时上下限;http code 有的按照具体的数量,有的按照百分比;
告警提示的样例,随便找了张图:
7. 总结 #
在实际的实现过程中,就要复杂的很多,要考虑的因素也更多。就现在市面上的各种前端监控系统,着重点也不一样,支持的平台也不一样。
比如我曾经使用过的 RUM(之前叫 Aegis),支持的功能就比较多,更多是在前端领域。我摘抄一下RUM 官方的介绍:
前端性能监控(Real User Monitoring,RUM)是一站式前端监控解决方案,专注于 Web、小程序等场景监控。前端性能监控聚焦用户页面性能(页面测速,接口测速,CDN 测速等)和质量(JS 错误,Ajax 错误等),并且联动腾讯云应用性能观测实现前后端一体化监控。用户只需要安装 SDK 到自己的项目中,通过简单配置化,即可实现对用户页面质量的全方位守护,真正做到低成本使用和无侵入监控。
但我后来了解到的 Sentry,它是更聚焦在错误日志收集和全链路追踪上,支持前端 js、Node.js、Python、Go 等各种语言环境。如每条的错误信息,除了一些全局数据(如浏览器版本等),还有收集发生该错误前的一些接口请求、console 日志输出和用户的点击行为等,方便我们可以尽可能的还原现场。同时,还对每条的 xhr 请求添加 trace 等请求头参数,便于追踪该请求的整条链路的处理。