// 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 = 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); }); }