Files
vf_react/src/utils/performanceMonitor.ts
zdl e02cbcd9b7 feat(性能监控): 补全 T0 标记 + PostHog 上报
- index.js: 添加 html-loaded 标记(T0 监控点)
- performanceMonitor.ts: 调用 reportPerformanceMetrics 上报到 PostHog
- 现在完整监控 T0-T5 全部阶段并上报性能指标

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 18:29:35 +08:00

381 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/utils/performanceMonitor.ts
// 性能监控工具 - 统计白屏时间和性能指标
import { logger } from './logger';
import { reportPerformanceMetrics } from '../lib/posthog';
/**
* 性能指标接口
*/
export interface PerformanceMetrics {
// 网络指标
dns?: number; // DNS查询时间
tcp?: number; // TCP连接时间
ttfb?: number; // 首字节时间(Time To First Byte)
domLoad?: number; // DOM加载时间
resourceLoad?: number; // 资源加载时间
// 渲染指标
fp?: number; // 首次绘制(First Paint)
fcp?: number; // 首次内容绘制(First Contentful Paint)
lcp?: number; // 最大内容绘制(Largest Contentful Paint)
// 自定义指标
reactInit?: number; // React初始化时间
authCheck?: number; // 认证检查时间
homepageRender?: number; // 首页渲染时间
// 总计
totalWhiteScreen?: number; // 总白屏时间
}
/**
* 性能报告接口
*/
export interface PerformanceReport {
summary: {
performanceScore: string;
totalMarks: number;
totalMeasures: number;
};
metrics: PerformanceMetrics;
recommendations: string[];
marks: Array<{ name: string; time: number }>;
measures: Array<{ name: string; duration: number; startMark: string; endMark: string }>;
}
/**
* 性能时间点记录
*/
const performanceMarks: Map<string, number> = new Map();
/**
* 性能测量记录
*/
const performanceMeasures: Array<{ name: string; duration: number; startMark: string; endMark: string }> = [];
/**
* 性能监控器类
*/
class PerformanceMonitor {
private metrics: PerformanceMetrics = {};
/**
* 标记性能时间点
*/
mark(name: string): void {
const timestamp = performance.now();
performanceMarks.set(name, timestamp);
}
/**
* 计算两个时间点之间的耗时
*/
measure(startMark: string, endMark: string, name?: string): number | null {
const startTime = performanceMarks.get(startMark);
const endTime = performanceMarks.get(endMark);
if (!startTime || !endTime) {
logger.warn('PerformanceMonitor', 'Missing performance mark', {
startMark,
endMark,
hasStart: !!startTime,
hasEnd: !!endTime
});
return null;
}
const duration = endTime - startTime;
const measureName = name || `${startMark}${endMark}`;
// 记录测量
performanceMeasures.push({
name: measureName,
duration,
startMark,
endMark
});
return duration;
}
/**
* 获取浏览器性能指标
*/
collectBrowserMetrics(): void {
if (!window.performance || !window.performance.timing) {
logger.warn('PerformanceMonitor', 'Performance API not supported');
return;
}
const timing = window.performance.timing;
const navigationStart = timing.navigationStart;
// 网络指标
this.metrics.dns = timing.domainLookupEnd - timing.domainLookupStart;
this.metrics.tcp = timing.connectEnd - timing.connectStart;
this.metrics.ttfb = timing.responseStart - navigationStart;
this.metrics.domLoad = timing.domContentLoadedEventEnd - navigationStart;
this.metrics.resourceLoad = timing.loadEventEnd - navigationStart;
// 获取 FP/FCP/LCP
this.collectPaintMetrics();
}
/**
* 收集绘制指标(FP/FCP/LCP)
*/
private collectPaintMetrics(): void {
if (!window.performance || !window.performance.getEntriesByType) {
return;
}
// First Paint & First Contentful Paint
const paintEntries = performance.getEntriesByType('paint');
paintEntries.forEach((entry: any) => {
if (entry.name === 'first-paint') {
this.metrics.fp = entry.startTime;
} else if (entry.name === 'first-contentful-paint') {
this.metrics.fcp = entry.startTime;
}
});
// Largest Contentful Paint (需要 PerformanceObserver)
try {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1] as any;
this.metrics.lcp = lastEntry.startTime;
});
observer.observe({ entryTypes: ['largest-contentful-paint'] });
} catch (e) {
// LCP可能不被支持
}
}
/**
* 收集自定义React指标
*/
collectReactMetrics(): void {
// React初始化时间
const reactInit = this.measure('app-start', 'react-ready', 'React初始化');
if (reactInit) this.metrics.reactInit = reactInit;
// 认证检查时间
const authCheck = this.measure('auth-check-start', 'auth-check-end', '认证检查');
if (authCheck) this.metrics.authCheck = authCheck;
// 首页渲染时间
const homepageRender = this.measure('homepage-render-start', 'homepage-render-end', '首页渲染');
if (homepageRender) this.metrics.homepageRender = homepageRender;
// 计算总白屏时间 (从页面开始到首屏完成)
const totalWhiteScreen = this.measure('app-start', 'homepage-render-end', '总白屏时间');
if (totalWhiteScreen) this.metrics.totalWhiteScreen = totalWhiteScreen;
}
/**
* 生成性能报告(控制台版本)
*/
generateReport(): PerformanceMetrics {
this.collectBrowserMetrics();
this.collectReactMetrics();
const report = {
'=== 网络阶段 ===': {
'DNS查询': this.formatMs(this.metrics.dns),
'TCP连接': this.formatMs(this.metrics.tcp),
'首字节时间(TTFB)': this.formatMs(this.metrics.ttfb),
'DOM加载': this.formatMs(this.metrics.domLoad),
'资源加载': this.formatMs(this.metrics.resourceLoad),
},
'=== 渲染阶段 ===': {
'首次绘制(FP)': this.formatMs(this.metrics.fp),
'首次内容绘制(FCP)': this.formatMs(this.metrics.fcp),
'最大内容绘制(LCP)': this.formatMs(this.metrics.lcp),
},
'=== React阶段 ===': {
'React初始化': this.formatMs(this.metrics.reactInit),
'认证检查': this.formatMs(this.metrics.authCheck),
'首页渲染': this.formatMs(this.metrics.homepageRender),
},
'=== 总计 ===': {
'总白屏时间': this.formatMs(this.metrics.totalWhiteScreen),
}
};
logger.info('PerformanceMonitor', '🚀 性能报告', report);
// 性能分析建议
this.analyzePerformance();
// 上报性能指标到 PostHog
reportPerformanceMetrics(this.metrics);
return this.metrics;
}
/**
* 获取完整报告UI版本
*/
getReport(): PerformanceReport {
this.collectBrowserMetrics();
this.collectReactMetrics();
const recommendations = this.getRecommendations();
const performanceScore = this.calculatePerformanceScore();
return {
summary: {
performanceScore,
totalMarks: performanceMarks.size,
totalMeasures: performanceMeasures.length,
},
metrics: this.metrics,
recommendations,
marks: Array.from(performanceMarks.entries()).map(([name, time]) => ({ name, time })),
measures: performanceMeasures,
};
}
/**
* 计算性能评分
*/
private calculatePerformanceScore(): string {
const { totalWhiteScreen, ttfb, lcp } = this.metrics;
let score = 0;
let total = 0;
if (totalWhiteScreen !== undefined) {
total += 1;
if (totalWhiteScreen < 1500) score += 1;
else if (totalWhiteScreen < 2000) score += 0.7;
else if (totalWhiteScreen < 3000) score += 0.4;
}
if (ttfb !== undefined) {
total += 1;
if (ttfb < 500) score += 1;
else if (ttfb < 1000) score += 0.7;
else if (ttfb < 1500) score += 0.4;
}
if (lcp !== undefined) {
total += 1;
if (lcp < 2500) score += 1;
else if (lcp < 4000) score += 0.7;
else if (lcp < 6000) score += 0.4;
}
if (total === 0) return 'unknown';
const percentage = score / total;
if (percentage >= 0.9) return 'excellent';
if (percentage >= 0.7) return 'good';
if (percentage >= 0.5) return 'needs improvement';
return 'poor';
}
/**
* 获取优化建议
*/
private getRecommendations(): string[] {
const issues: string[] = [];
if (this.metrics.ttfb && this.metrics.ttfb > 500) {
issues.push(`TTFB过高(${this.metrics.ttfb.toFixed(0)}ms) - 建议优化服务器响应`);
}
if (this.metrics.resourceLoad && this.metrics.resourceLoad > 3000) {
issues.push(`资源加载慢(${this.metrics.resourceLoad.toFixed(0)}ms) - 建议代码分割/CDN`);
}
if (this.metrics.authCheck && this.metrics.authCheck > 300) {
issues.push(`认证检查慢(${this.metrics.authCheck.toFixed(0)}ms) - 建议优化Session API`);
}
if (this.metrics.totalWhiteScreen && this.metrics.totalWhiteScreen > 2000) {
issues.push(`白屏时间过长(${this.metrics.totalWhiteScreen.toFixed(0)}ms) - 目标 <1500ms`);
}
if (this.metrics.lcp && this.metrics.lcp > 2500) {
issues.push(`LCP过高(${this.metrics.lcp.toFixed(0)}ms) - 建议优化最大内容渲染`);
}
if (this.metrics.fcp && this.metrics.fcp > 1800) {
issues.push(`FCP过高(${this.metrics.fcp.toFixed(0)}ms) - 建议优化首屏内容渲染`);
}
if (issues.length === 0) {
issues.push('性能表现良好,无需优化');
}
return issues;
}
/**
* 性能分析和建议
*/
private analyzePerformance(): void {
const issues = this.getRecommendations();
if (issues.length > 0 && issues[0] !== '性能表现良好,无需优化') {
logger.warn('PerformanceMonitor', '🔴 性能问题', { issues });
} else {
logger.info('PerformanceMonitor', '✅ 性能良好');
}
}
/**
* 格式化毫秒数
*/
private formatMs(ms: number | undefined): string {
if (ms === undefined) return 'N/A';
let emoji = '✅';
if (ms > 1000) emoji = '❌';
else if (ms > 500) emoji = '⚠️';
return `${ms.toFixed(0)}ms ${emoji}`;
}
/**
* 导出 JSON
*/
exportJSON(): string {
const report = this.getReport();
return JSON.stringify(report, null, 2);
}
/**
* 获取所有指标
*/
getMetrics(): PerformanceMetrics {
return this.metrics;
}
/**
* 重置监控器
*/
reset(): void {
this.metrics = {};
performanceMarks.clear();
performanceMeasures.length = 0;
}
}
// 导出单例
export const performanceMonitor = new PerformanceMonitor();
// 页面加载完成后自动生成报告
if (typeof window !== 'undefined') {
window.addEventListener('load', () => {
// 延迟1秒确保所有指标收集完成
setTimeout(() => {
performanceMonitor.generateReport();
}, 1000);
});
}