Appearance
自定义性能监控
第一部分:监控系统设计
1.1 监控架构
javascript
// 完整的性能监控系统架构
class PerformanceMonitor {
constructor(config = {}) {
this.config = {
endpoint: '/api/performance',
sampleRate: 0.1, // 采样率10%
batchSize: 10,
batchInterval: 5000,
...config
};
this.metrics = [];
this.batchTimer = null;
this.init();
}
init() {
this.trackWebVitals();
this.trackCustomMetrics();
this.trackErrors();
this.trackResources();
this.startBatching();
}
trackWebVitals() {
import('web-vitals').then(({ onCLS, onFID, onLCP, onFCP, onTTFB }) => {
onLCP(this.handleMetric.bind(this));
onFID(this.handleMetric.bind(this));
onCLS(this.handleMetric.bind(this));
onFCP(this.handleMetric.bind(this));
onTTFB(this.handleMetric.bind(this));
});
}
trackCustomMetrics() {
// 自定义业务指标
this.trackComponentRender();
this.trackAPIRequests();
this.trackUserInteractions();
}
trackErrors() {
window.addEventListener('error', (event) => {
this.record({
type: 'error',
message: event.message,
filename: event.filename,
line: event.lineno,
column: event.colno
});
});
window.addEventListener('unhandledrejection', (event) => {
this.record({
type: 'unhandled-rejection',
reason: event.reason
});
});
}
trackResources() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
this.record({
type: 'resource',
name: entry.name,
duration: entry.duration,
size: entry.transferSize
});
}
}
});
observer.observe({ entryTypes: ['resource'] });
}
handleMetric(metric) {
if (Math.random() > this.config.sampleRate) {
return; // 采样
}
this.record({
type: 'web-vital',
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta
});
}
record(data) {
this.metrics.push({
...data,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
});
if (this.metrics.length >= this.config.batchSize) {
this.flush();
}
}
startBatching() {
this.batchTimer = setInterval(() => {
if (this.metrics.length > 0) {
this.flush();
}
}, this.config.batchInterval);
}
flush() {
if (this.metrics.length === 0) return;
const data = [...this.metrics];
this.metrics = [];
if (navigator.sendBeacon) {
navigator.sendBeacon(
this.config.endpoint,
JSON.stringify(data)
);
} else {
fetch(this.config.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
keepalive: true
});
}
}
trackComponentRender() {
// React Profiler集成
}
trackAPIRequests() {
// API请求监控
}
trackUserInteractions() {
// 用户交互追踪
}
}
// 使用
const monitor = new PerformanceMonitor({
endpoint: '/api/metrics',
sampleRate: 0.2
});1.2 React集成
javascript
// React性能监控组件
function PerformanceProvider({ children }) {
const metricsRef = useRef([]);
useEffect(() => {
// 监控Web Vitals
import('web-vitals').then(({ onCLS, onFID, onLCP }) => {
onLCP((metric) => recordMetric('LCP', metric));
onFID((metric) => recordMetric('FID', metric));
onCLS((metric) => recordMetric('CLS', metric));
});
// 页面卸载时发送数据
const handleUnload = () => {
if (metricsRef.current.length > 0) {
sendMetrics(metricsRef.current);
}
};
window.addEventListener('beforeunload', handleUnload);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
handleUnload();
}
});
return () => {
window.removeEventListener('beforeunload', handleUnload);
};
}, []);
const recordMetric = (name, metric) => {
metricsRef.current.push({
name,
value: metric.value,
rating: metric.rating,
timestamp: Date.now()
});
};
const sendMetrics = (metrics) => {
navigator.sendBeacon(
'/api/performance',
JSON.stringify(metrics)
);
};
return children;
}
// 使用
function App() {
return (
<PerformanceProvider>
<MyApp />
</PerformanceProvider>
);
}1.3 自定义指标
javascript
// 业务关键指标
function useBusinessMetrics() {
useEffect(() => {
// 首次有意义的绘制
performance.mark('meaningful-paint');
// 用户可交互时间
window.addEventListener('load', () => {
performance.mark('interactive');
performance.measure(
'time-to-interactive',
'navigationStart',
'interactive'
);
});
// 自定义业务指标
trackCheckoutFlow();
trackSearchPerformance();
trackVideoPlayback();
}, []);
}
// 结账流程监控
function trackCheckoutFlow() {
performance.mark('checkout-start');
// 各步骤标记
const steps = [
'cart-loaded',
'shipping-entered',
'payment-entered',
'order-submitted'
];
steps.forEach(step => {
document.addEventListener(`checkout:${step}`, () => {
performance.mark(step);
performance.measure(
`checkout-${step}`,
'checkout-start',
step
);
});
});
}
// 搜索性能监控
function useSearchMetrics() {
const trackSearch = useCallback((query, results, duration) => {
const metric = {
type: 'search',
query,
resultCount: results.length,
duration,
timestamp: Date.now()
};
sendMetric(metric);
// 慢速搜索告警
if (duration > 1000) {
console.warn('Slow search:', query, duration);
}
}, []);
return trackSearch;
}
// 使用
function SearchComponent() {
const trackSearch = useSearchMetrics();
const handleSearch = async (query) => {
const startTime = performance.now();
const results = await performSearch(query);
const duration = performance.now() - startTime;
trackSearch(query, results, duration);
return results;
};
}第二部分:数据采集
2.1 Navigation Timing
javascript
// Navigation Timing API
function collectNavigationTiming() {
const perfData = performance.getEntriesByType('navigation')[0];
if (!perfData) return null;
return {
// DNS查询时间
dnsTime: perfData.domainLookupEnd - perfData.domainLookupStart,
// TCP连接时间
tcpTime: perfData.connectEnd - perfData.connectStart,
// TLS协商时间
tlsTime: perfData.secureConnectionStart > 0
? perfData.connectEnd - perfData.secureConnectionStart
: 0,
// 请求响应时间
requestTime: perfData.responseStart - perfData.requestStart,
// 响应时间
responseTime: perfData.responseEnd - perfData.responseStart,
// DOM处理时间
domTime: perfData.domComplete - perfData.domInteractive,
// 页面加载总时间
loadTime: perfData.loadEventEnd - perfData.fetchStart,
// DOMContentLoaded时间
domContentLoadedTime: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
// 首字节时间
ttfb: perfData.responseStart - perfData.requestStart
};
}
// React Hook
function useNavigationTiming() {
const [timing, setTiming] = useState(null);
useEffect(() => {
window.addEventListener('load', () => {
setTimeout(() => {
const navigationTiming = collectNavigationTiming();
setTiming(navigationTiming);
// 上报数据
sendMetric('navigation-timing', navigationTiming);
}, 0);
});
}, []);
return timing;
}2.2 Resource Timing
javascript
// Resource Timing API
function collectResourceTiming() {
const resources = performance.getEntriesByType('resource');
const resourceMetrics = resources.map(resource => ({
name: resource.name,
type: resource.initiatorType,
duration: resource.duration,
size: resource.transferSize,
cached: resource.transferSize === 0
}));
// 分类统计
const stats = {
scripts: resourceMetrics.filter(r => r.type === 'script'),
styles: resourceMetrics.filter(r => r.type === 'link' || r.type === 'css'),
images: resourceMetrics.filter(r => r.type === 'img'),
fetch: resourceMetrics.filter(r => r.type === 'fetch'),
other: resourceMetrics.filter(r =>
!['script', 'link', 'css', 'img', 'fetch'].includes(r.type)
)
};
return {
resources: resourceMetrics,
stats,
totalSize: resourceMetrics.reduce((sum, r) => sum + r.size, 0),
totalDuration: resourceMetrics.reduce((sum, r) => sum + r.duration, 0)
};
}
// 监控慢速资源
function monitorSlowResources() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 1000) { // >1s的资源
console.warn('Slow resource:', {
name: entry.name,
duration: entry.duration,
size: entry.transferSize
});
sendAlert('slow-resource', {
url: entry.name,
duration: entry.duration
});
}
}
});
observer.observe({ entryTypes: ['resource'] });
}2.3 User Timing
javascript
// User Timing API
function usePerformanceTiming() {
const mark = useCallback((name) => {
performance.mark(name);
}, []);
const measure = useCallback((name, startMark, endMark) => {
performance.measure(name, startMark, endMark);
const measure = performance.getEntriesByName(name, 'measure').pop();
if (measure) {
sendMetric('custom-timing', {
name,
duration: measure.duration,
startTime: measure.startTime
});
}
return measure?.duration;
}, []);
const clear = useCallback(() => {
performance.clearMarks();
performance.clearMeasures();
}, []);
return { mark, measure, clear };
}
// 使用示例
function DataLoader() {
const { mark, measure } = usePerformanceTiming();
const [data, setData] = useState(null);
const loadData = async () => {
mark('data-fetch-start');
const result = await fetch('/api/data');
const json = await result.json();
mark('data-fetch-end');
const duration = measure('data-fetch', 'data-fetch-start', 'data-fetch-end');
console.log('Data fetch took:', duration, 'ms');
setData(json);
};
useEffect(() => {
loadData();
}, []);
return data ? <Display data={data} /> : <Loading />;
}
// 组件渲染性能
function ComponentPerformance({ children, name }) {
const renderCount = useRef(0);
const { mark, measure } = usePerformanceTiming();
const markStart = `${name}-render-${renderCount.current}-start`;
const markEnd = `${name}-render-${renderCount.current}-end`;
mark(markStart);
useEffect(() => {
mark(markEnd);
const duration = measure(`${name}-render-${renderCount.current}`, markStart, markEnd);
if (duration > 16) { // >16ms (60fps阈值)
console.warn(`${name} slow render:`, duration, 'ms');
}
renderCount.current++;
});
return children;
}第三部分:实时监控
3.1 性能仪表板
javascript
// 实时性能仪表板
function PerformanceDashboard() {
const [metrics, setMetrics] = useState({
lcp: null,
fid: null,
cls: null,
memoryUsage: null,
fps: null
});
useEffect(() => {
// Web Vitals
onLCP((metric) => setMetrics(m => ({ ...m, lcp: metric.value })));
onFID((metric) => setMetrics(m => ({ ...m, fid: metric.value })));
onCLS((metric) => setMetrics(m => ({ ...m, cls: metric.value })));
// 内存使用
if (performance.memory) {
const updateMemory = () => {
setMetrics(m => ({
...m,
memoryUsage: performance.memory.usedJSHeapSize / (1024 * 1024)
}));
};
const memoryInterval = setInterval(updateMemory, 1000);
return () => clearInterval(memoryInterval);
}
// FPS监控
let lastTime = performance.now();
let frames = 0;
const measureFPS = () => {
frames++;
const currentTime = performance.now();
if (currentTime >= lastTime + 1000) {
setMetrics(m => ({ ...m, fps: frames }));
frames = 0;
lastTime = currentTime;
}
requestAnimationFrame(measureFPS);
};
requestAnimationFrame(measureFPS);
}, []);
return (
<div className="performance-dashboard">
<MetricCard title="LCP" value={metrics.lcp} unit="ms" threshold={2500} />
<MetricCard title="FID" value={metrics.fid} unit="ms" threshold={100} />
<MetricCard title="CLS" value={metrics.cls} unit="" threshold={0.1} />
<MetricCard title="Memory" value={metrics.memoryUsage} unit="MB" />
<MetricCard title="FPS" value={metrics.fps} unit="" threshold={60} />
</div>
);
}
function MetricCard({ title, value, unit, threshold }) {
const status = threshold
? (value <= threshold ? 'good' : 'poor')
: 'neutral';
return (
<div className={`metric-card ${status}`}>
<h3>{title}</h3>
<div className="value">
{value !== null ? value.toFixed(2) : '--'}
{unit && <span className="unit">{unit}</span>}
</div>
{threshold && (
<div className="threshold">
目标: ≤{threshold}{unit}
</div>
)}
</div>
);
}3.2 异常检测
javascript
// 性能异常检测
class PerformanceAnomalyDetector {
constructor() {
this.baseline = {};
this.threshold = 1.5; // 超过基线1.5倍视为异常
}
setBaseline(metric, value) {
this.baseline[metric] = value;
}
check(metric, value) {
const baseline = this.baseline[metric];
if (!baseline) {
this.setBaseline(metric, value);
return false;
}
const ratio = value / baseline;
if (ratio > this.threshold) {
this.reportAnomaly(metric, value, baseline, ratio);
return true;
}
return false;
}
reportAnomaly(metric, value, baseline, ratio) {
console.warn('Performance anomaly detected:', {
metric,
current: value,
baseline,
ratio: ratio.toFixed(2)
});
// 发送告警
fetch('/api/alerts', {
method: 'POST',
body: JSON.stringify({
type: 'performance-anomaly',
metric,
value,
baseline,
timestamp: Date.now()
})
});
}
}
// 使用
const detector = new PerformanceAnomalyDetector();
onLCP((metric) => {
const isAnomaly = detector.check('LCP', metric.value);
if (isAnomaly) {
// 触发详细分析
captureDetailedMetrics();
}
});3.3 趋势分析
javascript
// 性能趋势追踪
class PerformanceTrendTracker {
constructor(windowSize = 100) {
this.windowSize = windowSize;
this.data = {
lcp: [],
fid: [],
cls: []
};
}
add(metric, value) {
if (!this.data[metric]) {
this.data[metric] = [];
}
this.data[metric].push({
value,
timestamp: Date.now()
});
// 保持窗口大小
if (this.data[metric].length > this.windowSize) {
this.data[metric].shift();
}
}
getStats(metric) {
const values = this.data[metric].map(d => d.value);
if (values.length === 0) return null;
const sorted = [...values].sort((a, b) => a - b);
return {
mean: values.reduce((a, b) => a + b) / values.length,
median: sorted[Math.floor(sorted.length / 2)],
p75: sorted[Math.floor(sorted.length * 0.75)],
p95: sorted[Math.floor(sorted.length * 0.95)],
min: sorted[0],
max: sorted[sorted.length - 1]
};
}
getTrend(metric, period = 10) {
const recent = this.data[metric].slice(-period);
if (recent.length < 2) return 'stable';
const recentAvg = recent.reduce((sum, d) => sum + d.value, 0) / recent.length;
const previousAvg = this.data[metric]
.slice(-period * 2, -period)
.reduce((sum, d) => sum + d.value, 0) / period;
const change = (recentAvg - previousAvg) / previousAvg;
if (change > 0.1) return 'degrading';
if (change < -0.1) return 'improving';
return 'stable';
}
}
// 使用
const tracker = new PerformanceTrendTracker();
onLCP((metric) => {
tracker.add('lcp', metric.value);
const stats = tracker.getStats('lcp');
const trend = tracker.getTrend('lcp');
console.log('LCP stats:', stats);
console.log('LCP trend:', trend);
});注意事项
1. 采样策略
javascript
// 生产环境采样
const shouldSample = () => {
// 10%采样
if (Math.random() > 0.1) return false;
// 排除爬虫
const isBot = /bot|crawler|spider/i.test(navigator.userAgent);
if (isBot) return false;
return true;
};
if (shouldSample()) {
setupPerformanceMonitoring();
}2. 数据隐私
javascript
// 匿名化敏感信息
function sanitizeURL(url) {
const parsed = new URL(url);
// 移除查询参数中的敏感信息
['token', 'key', 'password'].forEach(param => {
parsed.searchParams.delete(param);
});
return parsed.toString();
}
// 收集前处理
function collectMetric(metric) {
return {
...metric,
url: sanitizeURL(window.location.href),
// 不收集用户标识
};
}3. 性能影响
javascript
// 监控本身不应影响性能
// 1. 异步发送数据
// 2. 使用sendBeacon
// 3. 批量上报
// 4. 采样收集常见问题
Q1: 应该监控哪些指标?
A: Core Web Vitals + 业务关键指标。
Q2: 如何减少监控开销?
A: 采样、批量发送、异步处理。
Q3: 真实用户数据和实验室数据哪个重要?
A: 都重要,RUM反映真实情况,实验室数据便于复现。
Q4: 多久审查一次性能数据?
A: 建议每周审查,重大问题实时告警。
Q5: 如何设定告警阈值?
A: 基于历史数据和业务目标。
Q6: 性能监控影响隐私吗?
A: 需匿名化敏感信息,遵守隐私政策。
Q7: 如何处理性能数据?
A: 存储、聚合、可视化、分析、告警。
Q8: 移动端需要特殊处理吗?
A: 是的,网络和设备条件不同。
Q9: 如何验证监控系统有效性?
A: 模拟性能问题,检查是否正确检测和上报。
Q10: React 19的监控注意事项?
A: 注意并发特性和Suspense的性能模式。
总结
核心要点
1. 监控系统设计
✅ 全面的指标收集
✅ 实时数据处理
✅ 异常检测告警
✅ 趋势分析
2. 数据采集
✅ Web Vitals
✅ Navigation Timing
✅ Resource Timing
✅ 自定义指标
3. 实施要点
✅ 采样策略
✅ 批量上报
✅ 隐私保护
✅ 性能影响最小化最佳实践
1. 监控策略
✅ 覆盖关键指标
✅ 真实用户监控
✅ 实验室测试补充
✅ 持续优化
2. 数据处理
✅ 实时聚合
✅ 趋势分析
✅ 异常告警
✅ 可视化展示
3. 优化闭环
✅ 监控发现问题
✅ 分析定位原因
✅ 优化解决问题
✅ 验证优化效果自定义性能监控是持续优化的基础,建立完善的监控体系能及时发现和解决性能问题。