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

前言

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

相关推荐
ZzMemory8 分钟前
JavaScript 类数组:披着数组外衣的 “伪装者”?
前端·javascript·面试
helloworld工程师9 分钟前
Dubbo应用开发之架构的演进之路
架构·dubbo
启山智软11 分钟前
什么是单体架构?什么是微服务架构?
微服务·架构
梁萌19 分钟前
前端UI组件库
前端·ui
鲸渔23 分钟前
CSS高频属性速查指南
前端·css·css3
小高00724 分钟前
🌐AST(抽象语法树):前端开发的“代码编译器”
前端·javascript·面试
蓝易云24 分钟前
Git stash命令的详细使用说明及案例分析。
前端·git·后端
GIS瞧葩菜26 分钟前
Cesium 中拾取 3DTiles 交点坐标
前端·javascript·cesium
Allen Bright26 分钟前
【JS-7-ajax】AJAX技术:现代Web开发的异步通信核心
前端·javascript·ajax
轻语呢喃31 分钟前
Mock : 没有后端也能玩的虚拟数据
前端·javascript·react.js