性能监控体系
监控类型
┌──────────────────────────────────────────────────────────────┐
│ 前端监控体系 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 性能监控 │ │ 错误监控 │ │
│ │ (Performance) │ │ (Errors) │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ • Web Vitals │ │ • JS 错误 │ │
│ │ • 自定义指标 │ │ • 资源加载错误 │ │
│ │ • 页面加载时间 │ │ • Promise 拒绝 │ │
│ │ • 接口响应时间 │ │ • 框架错误 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 行为监控 │ │ 业务监控 │ │
│ │ (Behavior) │ │ (Business) │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ • 用户路径 │ │ • 转化率 │ │
│ │ • 点击热图 │ │ • API 调用失败率 │ │
│ │ • 页面停留时间 │ │ • 关键操作成功率 │ │
│ │ • 滚动深度 │ │ • 活跃用户数 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
性能监控架构
┌──────────────────────────────────────────────────────────────┐
│ 性能监控架构 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ 浏览器 │ │
│ │ (采集端) │ │
│ └──────┬───────┘ │
│ │ │
│ │ 上报数据 │
│ ▼ │
│ ┌──────────────┐ │
│ │ 数据收集层 │ │
│ │ Beacon API │ │
│ │ sendBeacon │ │
│ └──────┬───────┘ │
│ │ │
│ │ 批量发送 │
│ ▼ │
│ ┌──────────────┐ │
│ │ 数据处理层 │ │
│ │ (服务端) │ │
│ └──────┬───────┘ │
│ │ │
│ │ 存储 & 分析 │
│ ▼ │
│ ┌──────────────┐ │
│ │ 数据存储 │ │
│ │ ES / Kafka │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 可视化平台 │ │
│ │ Grafana │ │
│ └──────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Performance API
核心 API
// 获取性能数据
const perfEntries = performance.getEntries();
// 获取特定资源
const resourceEntries = performance.getEntriesByType('resource');
// 获取导航数据
const navigation = performance.getEntriesByType('navigation')[0];
console.log('页面加载时间:', navigation.loadEventEnd - navigation.startTime);
console.log('DNS 解析:', navigation.domainLookupEnd - navigation.domainLookupStart);
console.log('TCP 连接:', navigation.connectEnd - navigation.connectStart);
console.log('DOM 解析:', navigation.domContentLoadedEventEnd - navigation.domLoading);
console.log('页面完全加载:', navigation.loadEventEnd - navigation.startTime);
Navigation Timing
// Navigation Timing API
const [navigation] = performance.getEntriesByType('navigation');
// 各阶段时间
const timings = {
// 重定向时间
redirect: navigation.redirectEnd - navigation.redirectStart,
// DNS 查询
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
// TCP 连接
tcp: navigation.connectEnd - navigation.connectStart,
// SSL 连接
ssl: navigation.secureConnectionStart > 0
? navigation.connectEnd - navigation.secureConnectionStart
: 0,
// 请求
request: navigation.responseStart - navigation.requestStart,
// 响应
response: navigation.responseEnd - navigation.responseStart,
// DOM 处理
domProcessing: navigation.domComplete - navigation.domLoading,
// DOM 内容加载
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
// 完全加载
loaded: navigation.loadEventEnd - navigation.startTime
};
Resource Timing
// Resource Timing API - 监控资源加载
const resources = performance.getEntriesByType('resource');
// 过滤特定资源
const jsFiles = resources.filter(r => r.name.endsWith('.js'));
const cssFiles = resources.filter(r => r.name.endsWith('.css'));
const images = resources.filter(r => /\.(png|jpg|jpeg|webp|svg)$/.test(r.name));
// 获取资源加载时间
jsFiles.forEach(resource => {
console.log({
name: resource.name,
duration: resource.duration, // 总耗时
dns: resource.domainLookupEnd - resource.domainLookupStart,
tcp: resource.connectEnd - resource.connectStart,
ttfb: resource.responseStart - resource.requestStart, // 首字节时间
download: resource.responseEnd - resource.responseStart
});
});
User Timing
// User Timing API - 自定义计时
performance.mark('operation-start');
// 执行操作
await doSomething();
performance.mark('operation-end');
performance.measure('操作耗时', 'operation-start', 'operation-end');
// 获取测量结果
const measures = performance.getEntriesByType('measure');
console.log(measures);
// 清除
performance.clearMarks();
performance.clearMeasures();
Web Vitals 监控
三大核心指标
import { onLCP, onFID, onCLS } from 'web-vitals';
// LCP - 最大内容绘制
onLCP(metric => {
console.log('LCP:', {
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
entries: metric.entries
});
// 上报
sendToAnalytics({
name: 'LCP',
value: metric.value,
rating: metric.rating
});
});
// FID - 首次输入延迟
onFID(metric => {
console.log('FID:', {
value: metric.value,
rating: metric.rating
});
sendToAnalytics({
name: 'FID',
value: metric.value
});
});
// CLS - 累积布局偏移
onCLS(metric => {
console.log('CLS:', {
value: metric.value,
rating: metric.rating,
entries: metric.entries
});
sendToAnalytics({
name: 'CLS',
value: metric.value
});
});
完整 Web Vitals 封装
// vitals.js
import { onLCP, onFID, onCLS, onFCP, onTTFB } from 'web-vitals';
const vitals = {
lcp: null,
fid: null,
cls: null,
fcp: null,
ttfb: null
};
function sendToAnalytics(metric) {
const body = {
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id,
url: window.location.href,
userAgent: navigator.userAgent
};
// 使用 sendBeacon 确保数据发送
navigator.sendBeacon('/analytics', JSON.stringify(body));
}
export function initVitals() {
onLCP(sendToAnalytics);
onFID(sendToAnalytics);
onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
}
export { vitals };
错误监控
全局错误处理
// window.onerror - 捕获未处理的同步错误
window.onerror = (message, source, lineno, colno, error) => {
console.error('Global error:', {
message,
source,
lineno,
colno,
error
});
// 上报错误
reportError({
type: 'error',
message,
source,
lineno,
colno,
stack: error?.stack
});
// 返回 true 阻止默认处理
return false;
};
// unhandledrejection - 捕获未处理的 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled Promise rejection:', event.reason);
reportError({
type: 'unhandledrejection',
reason: event.reason,
promise: event.promise
});
});
// 资源加载错误(img, script, link 等)
window.addEventListener('error', (event) => {
if (event.target !== window) {
// 资源加载错误
const target = event.target;
console.error('Resource error:', {
src: target.src || target.href,
tagName: target.tagName
});
reportError({
type: 'resource',
src: target.src || target.href,
tagName: target.tagName
});
}
}, true); // 捕获阶段
Vue 错误处理
// Vue 3
const app = createApp(App);
app.config.errorHandler = (err, instance, info) => {
console.error('Vue error:', err);
console.error('Component:', instance);
console.error('Info:', info);
reportError({
type: 'vue',
error: err.message,
stack: err.stack,
component: instance?.$options?.name,
info
});
};
// 异步错误
app.config.globalProperties.$throw = (error) => {
throw error;
};
React 错误处理
// React 16+ 错误边界
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('React error:', error, errorInfo);
reportError({
type: 'react',
error: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack
});
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>出错了</div>;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary fallback={<ErrorPage />}>
<MyComponent />
</ErrorBoundary>
Sentry 集成
基础配置
import * as Sentry from '@sentry/browser';
// 初始化
Sentry.init({
dsn: 'https://[email protected]/xxx',
// 环境
environment: process.env.NODE_ENV,
// 发布版本
release: process.env.VITE_APP_VERSION,
// 采样率
tracesSampleRate: 0.1, // 10% 的事务
// 过滤
beforeSend(event, hint) {
// 过滤敏感信息
if (event.request?.cookies) {
delete event.request.cookies;
}
// 过滤特定错误
const error = hint?.originalException;
if (error?.message?.includes('ResizeObserver')) {
return null; // 忽略
}
return event;
},
// 用户信息
setUser: {
id: user.id,
email: user.email,
username: user.name
}
});
手动上报
// 上报普通事件
Sentry.captureMessage('Something happened');
// 上报错误
try {
throw new Error('Something went wrong');
} catch (e) {
Sentry.captureException(e);
}
// 上报自定义上下文
Sentry.setContext('user', {
id: '123',
role: 'admin'
});
Sentry.setTag('page', 'checkout');
Sentry.setExtra('order_id', 'abc123');
// 手动追踪
const transaction = Sentry.startTransaction({
op: 'task',
name: 'Fetch user data'
});
try {
await doSomething();
} finally {
transaction.finish();
}
React 集成
// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import * as Sentry from '@sentry/react';
import App from './App';
Sentry.init({
dsn: 'https://[email protected]/xxx',
integrations: [
new Sentry.BrowserTracing(),
new Sentry.Replay()
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0
});
// 错误边界
const ErrorBoundary = Sentry.withErrorBoundary(ErrorBoundaryComponent, {
fallback: <div>出错了</div>,
showDialog: true
});
ReactDOM.createRoot(document.getElementById('root')).render(
<ErrorBoundary>
<App />
</ErrorBoundary>
);
自定义性能指标
自定义计时
// 页面性能类
class PagePerformance {
constructor() {
this.timings = {};
this.marks = {};
}
// 标记时间点
mark(name) {
this.marks[name] = performance.now();
}
// 测量时间
measure(name, startMark, endMark) {
const start = startMark ? this.marks[startMark] : 0;
const end = endMark ? this.marks[endMark] : performance.now();
this.timings[name] = end - start;
return this.timings[name];
}
// 获取所有指标
getMetrics() {
const navigation = performance.getEntriesByType('navigation')[0];
return {
// 标准指标
ttfb: navigation.responseStart - navigation.requestStart,
fcp: this.getFCP(),
domInteractive: navigation.domInteractive - navigation.fetchStart,
domComplete: navigation.domComplete - navigation.fetchStart,
loadComplete: navigation.loadEventEnd - navigation.fetchStart,
// 自定义指标
...this.timings
};
}
// 获取 FCP
getFCP() {
const entries = performance.getEntriesByType('paint');
const fcp = entries.find(entry => entry.name === 'first-contentful-paint');
return fcp?.startTime || 0;
}
// 上报
report() {
const metrics = this.getMetrics();
// 过滤无效值
const validMetrics = Object.fromEntries(
Object.entries(metrics).filter(([_, v]) => v > 0 && v < 60000)
);
navigator.sendBeacon('/analytics/performance', JSON.stringify({
url: location.href,
metrics: validMetrics,
timestamp: Date.now()
}));
}
}
export const pagePerformance = new PagePerformance();
API 性能监控
// API 监控中间件
class APIPerformance {
static monitor(fetch) {
return async (url, options) => {
const start = performance.now();
const startTime = new Date().toISOString();
try {
const response = await fetch(url, options);
const duration = performance.now() - start;
// 上报成功
this.report({
type: 'api',
method: options?.method || 'GET',
url,
status: response.status,
duration,
startTime,
success: response.ok
});
return response;
} catch (error) {
const duration = performance.now() - start;
// 上报错误
this.report({
type: 'api',
method: options?.method || 'GET',
url,
duration,
startTime,
success: false,
error: error.message
});
throw error;
}
};
}
static report(data) {
// 发送到监控系统
navigator.sendBeacon('/analytics/api', JSON.stringify(data));
}
}
// 使用
const originalFetch = window.fetch;
window.fetch = APIPerformance.monitor(originalFetch);
性能优化策略
优化优先级
┌──────────────────────────────────────────────────────────────┐
│ 性能优化优先级 │
├──────────────────────────────────────────────────────────────┤
│ │
│ P0 (立即优化) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • LCP > 4s → 优化服务器响应、关键资源加载 │ │
│ │ • FID > 300ms → 减少主线程阻塞 │ │
│ │ • CLS > 0.25 → 修复布局偏移 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ P1 (本周优化) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 资源体积优化(图片压缩、代码分割) │ │
│ │ • 缓存策略优化(Cache-Control、Service Worker) │ │
│ │ • 接口响应时间优化 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ P2 (计划优化) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 代码架构重构 │ │
│ │ • 引入新的优化技术(Streaming SSR) │ │
│ │ • 性能预算执行 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
常用优化手段
| 问题 | 解决方案 |
|---|---|
| LCP 慢 | 预加载关键资源、优化 TTFB、CDN |
| FID 高 | 代码分割、Web Worker、长任务分片 |
| CLS 大 | 图片尺寸、字体加载、动态内容 |
| JS 大 | Tree-shaking、压缩、代码分割 |
| 图片大 | WebP/AVIF、懒加载、响应式图片 |
这一章想说的
性能监控是持续优化的基础:
- Performance API:浏览器原生性能数据采集
- Web Vitals:Google 定义的三大核心指标(LCP/FID/CLS)
- 错误监控:JS 错误、Promise 拒绝、资源加载错误
- Sentry:一站式错误追踪平台
- 持续优化:发现问题 → 优化 → 监控验证
建立完善的监控体系,才能让性能优化有的放矢。