前端监控原理

监控上报策略

如果监控的上报策略是即时上报、一次上报一条,这种情况会导致上报请求过多。为了减少服务端压力、减少部分客户端对上报量的限制(小程序每秒只能上报 5 个请求),监控请求需聚合上报。 分为以下几种:

  • BatchIndexes:批量上报索引(包含性能等其他日志),可一次批量上报 N 条(最常使用的上报方法)

  • MomentIndexes:即时上报索引,一次全部上报

  • FeedbackIndexes:用户反馈索引,一次上报一条

页面关闭时

生命周期监测: beforeunload, unload, pagehide, and visibilitychange 由于页面关闭的时候将禁止发出 XHR 请求,将采用sendBeacon。 当用户关闭网页的时候,需要上报一些数据。很自然的做法是在 unload 事件或 beforeunload 事件的监听函数里面,使用 XMLHttpRequest 对象发送数据。但是,这样做不是很可靠,因为 XMLHttpRequest 对象是异步发送,很可能在它即将发送的时候,页面已经卸载了,从而导致发送取消或者发送失败。 解决方法就是 AJAX 通信改成同步发送,即只有发送完成,页面才能卸载。将请求改为同步以后,会阻塞页面关闭或重新加载的过程,这样就会影响用户体验。而且很多浏览器已经不支持同步的 XMLHttpRequest 对象了(即 open()方法的第三个参数为 false)。 为了解决上述问题,便有了 sendBeacon 方法,使用该方法发送请求,可以保证数据有效送达,且不会阻塞页面的卸载或加载,并且编码比起上述方法更加简单。

批量上报收益

  • 减少用户网络请求个数,小程序限制网络请求数,必须改为聚合上报

  • 减少服务端带宽成本和入口层吞吐。

代码混淆与 sourseMap

Source Map,顾名思义,是保存源代码映射关系的文件,使用 webpack 的开发者对它应该不会陌生。在项目开发完进行打包后,在打包的文件夹里通常除了 js,css,图片字体等资源文件外,还会有 xxx.js.map 的文件。这种带 map 后缀的文件就是 Source Map 文件——它保存了源代码和转换之后代码(通常经过压缩混淆和其他转换)的关系。 我们的代码在经过以下过程后往往会变得“面目全非”

  • 压缩混淆(UglifyJS)//压缩打包

  • 编译(TypeScript)//TS->JS

  • 转译(Babel)//ES6->ES5

  • 合并多个文件,减少带宽请求。//减少资源包大小

sourceMap 的格式

{
  version : 3, //SourceMap 的版本,目前为 3
  sources: ["foo.js", "bar.js"], //转换前的文件,该项是一个数组,表示可能存在多个文件合并
  names: ["src", "maps", "are", "fun"], //转换前的所有变量名和属性名
  mappings: "AACvB,gBAAgB,EAAE;AAClB;", //记录位置信息的字符串
  file: "out.js", //转换后的文件名
  sourcesContent: " \t// The module cache\n", //转换后的代码
  sourceRoot : "" //转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空
}

推荐两篇关于 sourceMap mappings 的文章,非常详细且好理解:

性能指标

  • FP (First Paint) 为首次渲染的时间点,在性能统计指标中,从用户开始访问 Web 页面的时间点到 FP 的时间点这段时间可以被视为 白屏时间 (通过 PerformancePaintTiming API 获取)

  • FCP (First Contentful Paint) 为首次有内容渲染的时间点,在性能统计指标中,从用户开始访问 Web 页面的时间点到 FCP 的时间点这段时间可以被视为 无内容时间 (通过 PerformancePaintTiming API 获取)

  • FMP(First Meaningful Paint) 即首次绘制有意义内容的时间,前端业界现在比较认可的一个计算 FMP 的方式就是「认定页面在加载和渲染过程中最大布局变动之后的那个绘制时间即为当前页面的 FMP 」(通过 MutationObserver API 获取)

  • TTI(Time To Interactive) 即从页面加载开始到页面处于完全可交互状态所花费的时间。 【RUM】如何计算 Web 页面的 TTI 指标 (通过算法获取)

异常监控

监控 Web 页面的 JS 运行时异常 JS 运行时异常通常分为以下几种类型:

  • EvalError

  • InternalError

  • RangeError

  • ReferenceError

  • SyntaxError

  • TypeError

  • URIError

可采用 Sentry 对 JS 运行时异常进行上报的。 当 JavaScript 运行时错误 发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror 回调函数。因此可以在 window.onerror 的回调函数中获取 JS 异常。

window.onerror = function (message, source, lineno, colno, error) {
  // Sentry 异常上报数据格式
};

监控 Web 页面的 HTTP 请求异常

HTTP 请求异常定义 HTTP 请求异常指的是 HTTP 请求失败或者 HTTP 请求返回的状态码非 20X,主要指的是 AJAX 或者 Fetch API 的 HTTP 请求。需要对原生的 XMLHttpRequest 对象和 fetch 方法进行重写,从而在代理对象或方法中实现状态码的监听和错误的上报。

监控崩溃

崩溃监控其实应该叫做【卡死监控】,其实现机制是通过 worker 线程每 2 秒向主线程发送一个心跳包来判断主线程是否卡死,超过 6s 没有心跳则认为已经 crash。

客户端 JS 主线程

固定时间间隔(2s)向 Web Worker 发送心跳

Web Worker

定期(2s)检查是否收到心跳。 超过一定时间(6s)未收到心跳,则认为页面崩溃。 检测到崩溃后,通过 HTTP 请求进行异常上报。

监控卡顿

实现原理:

持续每隔 1s 记录一次页面的 FPS 数值 每次记录 FPS 时候进行判断,如果连续 N 次页面的 FPS 数值小于 M,则认为页面发生了卡顿 目前卡顿监控是通过每隔一秒计算页面 FPS(屏幕每秒渲染的帧数),当页面 FPS 低于一定阈值时则上报为一次卡顿。Web 页面的卡顿一般在很多情况下往往是由于用户操作带来的,例如 click 或者 keydown。所以我们可以在事件捕获阶段,开始监控 FPS 的变化,从而判断是否引起了一次用户感知的到的卡顿。具体方式可以描述为:用户交互后渲染新的一帧需要的时间大于阈值 T 表示一次卡顿。 由于 FPS 的计算是通过 RAF(requestAnimationFrame)获取,加上每秒一次的定时计算,对一些比较重视性能的场景(如端内)消耗过大,且目前由于未知因素导致的卡顿误报率较高。

最后更新于