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

前言

我们前面已经简单的实现了错误监控、接口监控、流量监控这三个功能,我们今天实现前端性能相关的监控。性能相关的指标如果你还没有深入了解过,那么建议去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 分钟前
多场景落地复盘:端边云架构无人机智能巡检系统设计与实践
大数据·数据结构·人工智能·科技·架构·无人机·能源
用户9850335932839 分钟前
OpenLayers 热力图从原理到 Vue 组件一键接入
前端
颜酱42 分钟前
01 | 骨架搭建:FastAPI + Vue 跑通第一个 SSE 流式问答
前端·人工智能·后端
hoLzwEge1 小时前
解码 IDE 智能提示:jsconfig.json 辅助开发全攻略
前端·前端框架
leoZ2311 小时前
记忆系统与 Agent 定制完全指南(四):自定义 Agent 开发(一)
前端·chrome
慧一居士1 小时前
Element Plus 按需引入的配置使用说明和完整示例
前端·vue.js
程序员黑豆1 小时前
鸿蒙应用开发实战:从零学会自定义组件
前端·华为·harmonyos
leoZ2311 小时前
记忆系统与 Agent 定制完全指南(三):记忆的检索与使用
前端·chrome
遇乐的果园1 小时前
前端学习笔记-vue状态管理优化
前端·笔记·学习
GIS阵地2 小时前
QgsSingleBandPseudoColorRenderer 完整详解(QGIS 3.40.13 C++)
开发语言·前端·c++·qt·qgis