打造自己的前端监控---前端性能监控

前言

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

主要性能监控

因为有很多性能指标,我们就不一一去说了,只说三个比较核心的指标我们时如何采集计算的的,当然了这个是我们自己的计算方式,并不一定适合所有人。

我们先说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内部使用的依赖,但通过我们自己整理这个过程,我们能够很清晰的了解这些指标什么意思,是如何计算的。到此我们前端监控就全部完成了,当然了你也可以增加很多功能,比如采样率、忽略异常等。这个根据自己的需求去开发完成。

相关推荐
一只小风华~1 小时前
Vue Router 命名路由学习笔记
前端·javascript·vue.js·笔记·学习·ecmascript
我是华为OD~HR~栗栗呀1 小时前
前端面经-高级开发(华为od)
java·前端·后端·python·华为od·华为·面试
超级大只老咪2 小时前
HTML学习路线
前端·学习·html
゜ eVer ㄨ2 小时前
React学习第三天——生命周期
前端·学习·react.js
摆烂且佛系2 小时前
CSS元素的总宽度计算规则
前端·css
对岸住着星星2 小时前
vue3+ts实现拖拽缩放,全屏
前端·javascript
aesthetician2 小时前
@tanstack/react-query:React 服务器状态管理与数据同步解决方案
服务器·前端·react.js
Nan_Shu_6143 小时前
学习:uniapp全栈微信小程序vue3后台(28)
前端·学习·微信小程序·小程序·uni-app
珍宝商店3 小时前
原生 JavaScript 方法实战指南
开发语言·前端·javascript
蓝莓味的口香糖3 小时前
【企业微信】VUE项目在企微中自定义转发内容
前端·vue.js·企业微信