前言
我们前面已经简单的实现了错误监控、接口监控、流量监控这三个功能,我们今天实现前端性能相关的监控。性能相关的指标如果你还没有深入了解过,那么建议去core-web-vitals 这篇文章里面去了解一下。

主要性能监控
因为有很多性能指标,我们就不一一去说了,只说三个比较核心的指标我们时如何采集计算的的,当然了这个是我们自己的计算方式,并不一定适合所有人。
- Largest Contentful Paint (LCP):衡量加载性能。为了提供良好的用户体验,请尽力在网页开始加载的 2.5 秒内完成 LCP。
- The First Input Delay:FID 测量从用户首次与页面交互。
- Cumulative Layout Shift (CLS):衡量视觉稳定性。为了提供良好的用户体验,请尽力使 CLS 得分低于 0.1。
我们先说LCP,这里如何计算我们自己的系统的LCP?
LCP 会报告视口内可见的最大图片、文本块或视频的渲染时间(相对于用户首次导航到网页的时间)。
测量LCP,就是计算最大内容完成时来完成采集!
那我们的代码实现如下:
kotlin
export class LCPObserver {
private observer: PerformanceObserver;
private lcpDone: Boolean = true;
private opt: IParams;
private timer = null;
private visibilityWatcher = getVisibilityWatcher();
constructor(opts: IParams) {
this.opt = opts;
}
public IcpObserver(): void {
this.observer = new PerformanceObserver(this.entryHandler);
this.observer.observe({type: "largest-contentful-paint", buffered: true});
if (this && this.observer) {
// stop listening after input
["keydown", "click"].forEach(type => {
addEventListener(type, this.stopListening, {once: true, capture: true})
})
onHidden(this.stopListening);
}
}
private entryHandler = list => {
this.lcpDone = true;
let reprotData;
if (this.observer) {
this.observer.disconnect();
}
if (this.opt && this.opt.performanceIsEnabled) {
if (list && list.getEntries()) {
for (const entry of list.getEntries()) {
const json = entry.toJSON();
delete json.duration;
const value = entry.startTime;
if (value < this.visibilityWatcher.firstHiddenTime) {
json.value = value;
}
reprotData = {
...json,
pageUrl: getPageUrl(),
type: "performance",
name: entry.entryType
}
}
}
}
}
private stopListening = () => {
if (this && this.observer) {
this.observer.takeRecords().map(this.entryHandler);
this.observer.disconnect();
}
}
}
FID 测量从用户首次与页面交互(即,当他们单击链接、点击按钮或使用自定义的 JavaScript 控件时)到浏览器实际能够开始处理事件处理程序以响应该交互的时间。
那么我们如何实现呢?
ini
export default function observerFID(opt: IParams): void {
const visibilityWatcher = getVisibilityWatcher()
onBFCacheRestore(() => {
observerFID(opt)
});
const entryHandler:(list:any) => void = list => {
if (observer) {
observer.disconnect();
}
for (const entry of list.getEntries()) {
const value = entry.startTime;
const json = entry.toJSON();
if (value < visibilityWatcher.firstHiddenTime){
json.value = entry.processingStart - value
}
json.nodeName = entry.tagName;
json.event = json.name;
json.name = json.entryType
json.type = 'performance';
json.pageURL = getPageUrl()
delete json.cancelable
}
}
const observer = new PerformanceObserver(entryHandler)
observer.observe({type: "first-input", buffered: true})
}
function fidPolyfill(){
eachEventType(window.addEventListener)
}
function onInput(event){
if (event.cancelable) {
const isEpochTime = event.timeStamp > 1e12;
const now: number = isEpochTime ? Date.now() : performance.now();
const duration: number = now - event.timeStamp
}
eachEventType(window.removeEventListener)
}
function eachEventType(cb){
const eventTypes = [
'mousedown',
'keydown',
'touchstart',
];
eventTypes.forEach(event => {cb(event, onInput,{passive:true, capture:true})});
}
CLS 用于衡量在网页的整个生命周期内发生的每一次意外布局偏移的布局偏移得分的最高累计分数。简单来说就是从页面加载开始和生命周期状态变为隐藏期间发生的所有意外布局累积分数 公式: 布局偏移分数= 影响分数 * 距离分数
我们简单粗暴一些,取所有会话窗口中的最大值。
kotlin
export class CLSObserver {
private sessionValue: number = 0;
private sessionEntries = [];
private opt: IParams;
private cancel: string;
private observer: PerformanceObserver;
private cls = {
subType: "layout-shift",
name: "layout-shift",
type: "performance",
pageURL: getPageUrl(),
value: 0
};
constructor(opt: IParams, isCancel: string) {
this.opt = opt;
this.cancel = isCancel;
}
public clsObserver() {
this.sessionEntries = [];
this.sessionValue = 0;
this.observer = new PerformanceObserver(this.entryHandler);
this.observer.observe({type: "layout-shift", buffered: true});
if (this.observer) onHidden(() => {
this.observer.takeRecords().map(this.entryHandler)
}, true);
}
private entryHandler: (list: any) => void = list => {
if (this.opt && this.opt.performanceIsEnabled) {
for (const entry of list.getEntries()) {
if (entry && !entry.hadRecentInput) {
const firstSessionEntry = this.sessionEntries[0];
const lastSessionEntry = this.sessionEntries[this.sessionEntries.length - 1];
if (this.sessionEntries.length !== 0
&& entry.startTime - lastSessionEntry.startTime < 1000
&& entry.startTime - firstSessionEntry.startTime < 5000) {
this.sessionValue += entry.value
this.sessionEntries.push(this.formatCLSEntry(entry));
} else {
this.sessionValue = entry.value
this.sessionEntries = this.formatCLSEntry(entry);
}
if (this.sessionValue > this.cls.value) {
this.cls.value = this.sessionValue;
this.cls["entries"] = this.sessionEntries;
this.cls["startTime"] = performance.now();
}
}
}
}
}
private formatCLSEntry(entry: any) {
const res = entry.toJSON()
delete res.duration;
delete res.sources;
return res;
}
}
总结
当你做完这些,你会发现有个插件web-vitals可以帮我们实现我们所有想要的指标,这也是react内部使用的依赖,但通过我们自己整理这个过程,我们能够很清晰的了解这些指标什么意思,是如何计算的。到此我们前端监控就全部完成了,当然了你也可以增加很多功能,比如采样率、忽略异常等。这个根据自己的需求去开发完成。