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

前言

我们前面已经简单的实现了错误监控、接口监控、流量监控这三个功能,我们今天实现前端性能相关的监控。性能相关的指标如果你还没有深入了解过,那么建议去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内部使用的依赖,但通过我们自己整理这个过程,我们能够很清晰的了解这些指标什么意思,是如何计算的。到此我们前端监控就全部完成了,当然了你也可以增加很多功能,比如采样率、忽略异常等。这个根据自己的需求去开发完成。

相关推荐
花载酒4 分钟前
10个我离不开的 VSCode 插件
前端·visual studio code
uhakadotcom4 分钟前
next.js和vite的关系傻傻分不清,一文讲解区别
前端·面试·github
维基框架8 分钟前
维基框架 (Wiki FW) v1.1.1 | 企业级微服务开发框架
java·架构
小高00715 分钟前
一文吃透前端请求:XHR vs Fetch vs Axios,原理 + 实战 + 选型
前端·javascript·vue.js
跟橙姐学代码23 分钟前
配置文件这么多格式,Python到底该怎么选?一文带你梳理七种常见用法
前端·python·ipython
一乐小哥24 分钟前
大龄程序员的失业自救之路——Chrome 插件从注册到审核全程踩坑总结
前端·chrome·trae
子兮曰24 分钟前
🚀 Bun.js 2025终极入门指南:这个JavaScript运行时,让开发效率提升300%
前端·bun
李姆斯32 分钟前
数据与直播画面“神同步”——SEI(补充增强信息)
前端·webrtc·音视频开发
薛定谔的算法34 分钟前
面试官问hooks函数,如何高效准确的回答?
前端·react.js·面试
芒果味82234 分钟前
v-model和.sync的区别
前端·vue.js