1.前端监控---性能监控

58 阅读4分钟

一、性能监控概述

性能监控是优化网页性能、提升用户体验的关键步骤。通过监控页面的加载时间、资源加载、接口请求、用户交互、内存使用、页面卡顿等指标,开发者可以快速定位性能瓶颈并进行优化。

本文将介绍如何使用现代浏览器提供的 API(如 PerformanceNavigationTimingPerformanceObserver 等)实现全面的性能监控,并提供完整的代码示例。


二、核心性能监控指标

1. 页面加载性能

通过 PerformanceNavigationTiming API,可以获取页面加载的关键时间点,包括:

  • DCL(DOMContentLoaded) :DOM 加载完成的时间。
  • Load:页面完全加载的时间。
  • TTI(Time to Interactive) :页面可交互的时间。
  • DNS 查询时间:DNS 解析耗时。
  • TCP 连接时间:TCP 连接建立耗时。
  • 请求响应时间:从发起请求到接收到响应的时间。

示例代码

function getNavigationTiming() {
    const [navigationEntry] = performance.getEntriesByType('navigation');
    if (navigationEntry) {
        const {
            domContentLoadedEventEnd, // DCL
            loadEventEnd, // Load
            domInteractive, // TTI
            domainLookupStart,
            domainLookupEnd,
            connectStart,
            connectEnd,
            requestStart,
            responseStart,
            responseEnd,
            fetchStart,
        } = navigationEntry;

        console.log('DCL:', domContentLoadedEventEnd - fetchStart);
        console.log('Load:', loadEventEnd - fetchStart);
        console.log('TTI:', domInteractive - fetchStart);
        console.log('DNS 查询时间:', domainLookupEnd - domainLookupStart);
        console.log('TCP 连接时间:', connectEnd - connectStart);
        console.log('请求响应时间:', responseEnd - requestStart);
    }
}

window.addEventListener('load', getNavigationTiming);

2. 动态性能监控

通过 PerformanceObserver API,可以实时监控以下动态性能指标:

  • FCP(First Contentful Paint) :页面首次渲染内容的时间。
  • LCP(Largest Contentful Paint) :页面最大内容渲染的时间。
  • CLS(Cumulative Layout Shift) :页面布局偏移的累积值。
  • TBT(Total Blocking Time) :页面主线程被长任务阻塞的总时间。

示例代码

// FCP 监控
const fcpObserver = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    const fcpEntry = entries.find(entry => entry.name === 'first-contentful-paint');
    if (fcpEntry) {
        console.log('FCP:', fcpEntry.startTime);
    }
});
fcpObserver.observe({ type: 'paint', buffered: true });

// LCP 监控
const lcpObserver = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    const lcpEntry = entries[entries.length - 1];
    if (lcpEntry) {
        console.log('LCP:', lcpEntry.renderTime || lcpEntry.loadTime);
    }
});
lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });

// CLS 监控
let cls = 0;
const clsObserver = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    entries.forEach(entry => {
        if (!entry.hadRecentInput) {
            cls += entry.value;
            console.log('CLS:', cls);
        }
    });
});
clsObserver.observe({ type: 'layout-shift', buffered: true });

// TBT 监控
let tbt = 0;
const longTaskObserver = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    entries.forEach(entry => {
        const blockingTime = entry.duration - 50;
        if (blockingTime > 0) {
            tbt += blockingTime;
        }
    });
    console.log('TBT:', tbt);
});
longTaskObserver.observe({ type: 'longtask', buffered: true });

3. 帧率(FPS)监控

帧率(Frames Per Second, FPS)是衡量页面流畅度的重要指标。通过 requestAnimationFrame,可以实时计算页面的 FPS。

实现原理

  1. 使用 requestAnimationFrame 循环调用监控函数。
  2. 记录每一帧的时间戳。
  3. 计算每秒的帧数(FPS)。

示例代码

let fps = 0;
let frameCount = 0;
let lastTime = performance.now();

function checkFPS() {
    const now = performance.now();
    frameCount++;
    if (now > lastTime + 1000) { // 每秒钟计算一次 FPS
        fps = Math.round((frameCount * 1000) / (now - lastTime));
        console.log(`当前 FPS: ${fps}`);
        frameCount = 0;
        lastTime = now;
    }
    requestAnimationFrame(checkFPS);
}

// 启动 FPS 监控
requestAnimationFrame(checkFPS);

4. 资源加载性能

监控页面中所有资源(如 CSS、JavaScript、图片、字体等)的加载时间和加载状态。

实现方法
使用 PerformanceObserver 监听 resource 类型的性能条目。

示例代码

const resourceObserver = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    entries.forEach(entry => {
        console.log(`资源名称: ${entry.name}`);
        console.log(`资源类型: ${entry.initiatorType}`);
        console.log(`加载耗时: ${entry.duration.toFixed(2)}ms`);
        console.log(`传输大小: ${(entry.transferSize / 1024).toFixed(2)} KB`);
        console.log(`解码后大小: ${(entry.decodedBodySize / 1024).toFixed(2)} KB`);
        console.log('-----------------------------');
    });
});

// 开始监听资源加载
resourceObserver.observe({ type: 'resource', buffered: true });

5. 接口请求性能

监控页面中所有接口请求的响应时间、成功率和错误状态。支持 fetch 和 XMLHttpRequest

5.1 监控 fetch 请求

实现方法
重写 fetch 方法,拦截所有 fetch 请求。

示例代码

const originalFetch = window.fetch;
window.fetch = function (...args) {
    const startTime = performance.now();
    return originalFetch.apply(this, args)
        .then(response => {
            const duration = performance.now() - startTime;
            console.log(`fetch 请求 ${args[0]} 耗时: ${duration}ms`);
            return response;
        })
        .catch(error => {
            console.error(`fetch 请求 ${args[0]} 失败:`, error);
            throw error;
        });
};

5.2 监控 XMLHttpRequest 请求

实现方法
重写 XMLHttpRequest 的 open 和 send 方法,拦截所有 XMLHttpRequest 请求。

示例代码

// 简单版
const originalXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function () {
    const xhr = new originalXHR();
    const startTime = performance.now();

    xhr.addEventListener('load', function () {
        const duration = performance.now() - startTime;
        console.log(`XMLHttpRequest 请求 ${this.responseURL} 耗时: ${duration}ms`);
    });

    return xhr;
};
// 全面版
const originalXHR = window.XMLHttpRequest;

window.XMLHttpRequest = function () {
    const xhr = new originalXHR();
    let startTime;

    // 重写 open 方法
    const originalOpen = xhr.open;
    xhr.open = function (method, url) {
        this._method = method;
        this._url = url;
        originalOpen.apply(this, arguments);
    };

    // 重写 send 方法
    const originalSend = xhr.send;
    xhr.send = function (body) {
        startTime = performance.now(); // 记录请求开始时间
        this.addEventListener('load', onRequestComplete);
        this.addEventListener('error', onRequestError);
        this.addEventListener('abort', onRequestAbort);
        originalSend.apply(this, arguments);
    };

    // 请求完成时的回调
    function onRequestComplete() {
        const duration = performance.now() - startTime;
        console.log(`XMLHttpRequest 请求 ${this._method} ${this._url} 耗时: ${duration}ms`);
        this.removeEventListener('load', onRequestComplete);
        this.removeEventListener('error', onRequestError);
        this.removeEventListener('abort', onRequestAbort);
    }

    // 请求失败时的回调
    function onRequestError() {
        console.error(`XMLHttpRequest 请求 ${this._method} ${this._url} 失败`);
        this.removeEventListener('load', onRequestComplete);
        this.removeEventListener('error', onRequestError);
        this.removeEventListener('abort', onRequestAbort);
    }

    // 请求被取消时的回调
    function onRequestAbort() {
        console.warn(`XMLHttpRequest 请求 ${this._method} ${this._url} 被取消`);
        this.removeEventListener('load', onRequestComplete);
        this.removeEventListener('error', onRequestError);
        this.removeEventListener('abort', onRequestAbort);
    }

    return xhr;
};

6. 用户交互性能

监控用户点击、滚动、输入等操作的响应时间,以及长任务对交互的影响。

6.1 监控点击事件响应时间

实现方法
监听 click 事件,记录从事件触发到回调函数执行完成的时间。

示例代码

document.addEventListener('click', function () {
    const startTime = performance.now();
    requestAnimationFrame(() => {
        const duration = performance.now() - startTime;
        console.log(`点击事件响应时间: ${duration}ms`);
    });
});

6.2 监控滚动事件响应时间

实现方法
监听 scroll 事件,记录从事件触发到回调函数执行完成的时间。

示例代码

window.addEventListener('scroll', function () {
    const startTime = performance.now();
    requestAnimationFrame(() => {
        const duration = performance.now() - startTime;
        console.log(`滚动事件响应时间: ${duration}ms`);
    });
});

6.3 监控输入事件响应时间

实现方法
监听 input 事件,记录从事件触发到回调函数执行完成的时间。

示例代码

document.addEventListener('input', function () {
    const startTime = performance.now();
    requestAnimationFrame(() => {
        const duration = performance.now() - startTime;
        console.log(`输入事件响应时间: ${duration}ms`);
    });
});

7. 内存使用情况

监控页面的内存占用情况,检测内存泄漏。

实现方法
使用 performance.memory(仅限 Chrome)获取内存信息。

示例代码

if (performance.memory) {
    console.log(`内存使用: ${performance.memory.usedJSHeapSize} bytes`);
}

8. 页面卡顿检测

监控页面是否出现卡顿(如 FPS 低于 30),并记录卡顿的持续时间。

实现方法
结合 requestAnimationFrame 和 FPS 计算,检测卡顿。

示例代码

let lastTime = performance.now();
let frameCount = 0;
let isStuttering = false;

function checkStutter() {
    const now = performance.now();
    frameCount++;
    if (now > lastTime + 1000) {
        const fps = Math.round((frameCount * 1000) / (now - lastTime));
        if (fps < 30) {
            isStuttering = true;
            console.warn(`页面卡顿,FPS: ${fps}`);
        } else if (isStuttering) {
            isStuttering = false;
            console.log('页面恢复正常');
        }
        frameCount = 0;
        lastTime = now;
    }
    requestAnimationFrame(checkStutter);
}

requestAnimationFrame(checkStutter);

9. 自定义业务指标

监控关键业务操作的耗时(如表单提交、页面跳转)以及用户关键路径的性能(如从登录到下单的耗时)。

实现方法
在业务代码中手动埋点,记录关键操作的时间。

示例代码

function trackBusinessOperation(operationName) {
    const startTime = performance.now();
    return {
        end: () => {
            const duration = performance.now() - startTime;
            console.log(`${operationName} 耗时: ${duration}ms`);
        },
    };
}

const operation = trackBusinessOperation('表单提交');
// 业务逻辑...
operation.end();

三、总结

通过以上方法,您可以全面监控页面的性能,包括:

  1. 页面加载性能:DCL、Load、TTI、DNS 查询时间、TCP 连接时间、请求响应时间。
  2. 动态性能监控:FCP、LCP、CLS、TBT。
  3. 帧率监控:实时计算 FPS。
  4. 资源加载性能:监控所有资源的加载时间和状态。
  5. 接口请求性能:监控 fetch 和 XMLHttpRequest 的响应时间、成功率和错误状态。
  6. 用户交互性能:监控点击、滚动、输入等操作的响应时间。
  7. 内存使用情况:监控内存占用和内存泄漏。
  8. 页面卡顿检测:检测页面卡顿并记录持续时间。
  9. 自定义业务指标:监控关键业务操作的耗时。

这些监控点可以帮助您更好地优化页面性能,提升用户体验。