/** * Paper Burner - 本地性能监控工具 * Phase 4.4.3: 性能数据收集(仅本地,不上报) * * 使用方法: * - 启动监控:PerfMonitor.start() * - 停止监控:PerfMonitor.stop() * - 查看实时数据:PerfMonitor.getStats() * - 导出数据:PerfMonitor.export() * - 清空数据:PerfMonitor.clear() */ window.PerfMonitor = (function() { // 性能数据存储(仅内存,不持久化) var metrics = { renderTime: [], // 渲染耗时记录 fps: [], // FPS 记录 longTasks: [], // 长任务记录 memoryUsage: [], // 内存使用记录 scrollEvents: [], // 滚动事件密度 domNodes: [] // DOM 节点数量 }; // 监控状态 var state = { isRunning: false, startTime: null, fpsAnimationId: null, memoryIntervalId: null, longTaskObserver: null }; // 配置 var config = { maxSamples: 1000, // 每个指标最多保留样本数 memoryCheckInterval: 5000, // 内存检测间隔 (ms) fpsCheckInterval: 1000, // FPS 统计间隔 (ms) longTaskThreshold: 50 // 长任务阈值 (ms) }; /** * FPS 监控 */ function measureFPS() { var lastTime = performance.now(); var frames = 0; var lastRecordTime = lastTime; function loop() { if (!state.isRunning) return; frames++; var now = performance.now(); // 每秒统计一次 FPS if (now >= lastRecordTime + config.fpsCheckInterval) { var fps = Math.round((frames * 1000) / (now - lastRecordTime)); metrics.fps.push({ fps: fps, timestamp: Date.now() }); // 限制样本数量 if (metrics.fps.length > config.maxSamples) { metrics.fps.shift(); } frames = 0; lastRecordTime = now; } state.fpsAnimationId = requestAnimationFrame(loop); } state.fpsAnimationId = requestAnimationFrame(loop); } /** * 内存监控(仅 Chrome 支持) */ function measureMemory() { if (!performance.memory) return; try { metrics.memoryUsage.push({ used: performance.memory.usedJSHeapSize, total: performance.memory.totalJSHeapSize, limit: performance.memory.jsHeapSizeLimit, timestamp: Date.now() }); // 限制样本数量 if (metrics.memoryUsage.length > config.maxSamples) { metrics.memoryUsage.shift(); } } catch (e) { console.warn('[PerfMonitor] Memory API error:', e); } } /** * 长任务监控(需要 PerformanceObserver 支持) */ function observeLongTasks() { if (!window.PerformanceObserver) return; try { state.longTaskObserver = new PerformanceObserver(function(list) { var entries = list.getEntries(); for (var i = 0; i < entries.length; i++) { var entry = entries[i]; if (entry.duration >= config.longTaskThreshold) { metrics.longTasks.push({ duration: entry.duration, startTime: entry.startTime, timestamp: Date.now() }); // 限制样本数量 if (metrics.longTasks.length > config.maxSamples) { metrics.longTasks.shift(); } } } }); // 监控 longtask(需要浏览器支持) state.longTaskObserver.observe({ entryTypes: ['longtask'] }); } catch (e) { // longtask 类型可能不支持,降级为 measure try { state.longTaskObserver.observe({ entryTypes: ['measure'] }); } catch (e2) { console.warn('[PerfMonitor] PerformanceObserver not supported'); } } } /** * 记录渲染耗时 */ function recordRenderTime(duration, context) { if (!state.isRunning) return; metrics.renderTime.push({ duration: duration, context: context || 'unknown', timestamp: Date.now() }); // 限制样本数量 if (metrics.renderTime.length > config.maxSamples) { metrics.renderTime.shift(); } } /** * 记录 DOM 节点数量 */ function recordDOMNodes() { if (!state.isRunning) return; var chatBody = document.querySelector('.chat-body'); if (!chatBody) return; metrics.domNodes.push({ total: document.querySelectorAll('*').length, chatMessages: chatBody.querySelectorAll('.message').length, timestamp: Date.now() }); // 限制样本数量 if (metrics.domNodes.length > config.maxSamples) { metrics.domNodes.shift(); } } /** * 计算统计数据 */ function calculateStats(arr, key) { if (!arr || arr.length === 0) { return { count: 0, min: 0, max: 0, avg: 0, p50: 0, p95: 0, p99: 0 }; } var values = key ? arr.map(function(item) { return item[key]; }) : arr; var sorted = values.slice().sort(function(a, b) { return a - b; }); var sum = sorted.reduce(function(acc, val) { return acc + val; }, 0); return { count: sorted.length, min: sorted[0], max: sorted[sorted.length - 1], avg: sum / sorted.length, p50: sorted[Math.floor(sorted.length * 0.5)], p95: sorted[Math.floor(sorted.length * 0.95)], p99: sorted[Math.floor(sorted.length * 0.99)] }; } /** * 获取性能统计数据 */ function getStats() { var now = Date.now(); var duration = state.startTime ? (now - state.startTime) / 1000 : 0; return { session: { isRunning: state.isRunning, duration: duration.toFixed(1) + 's', startTime: state.startTime ? new Date(state.startTime).toISOString() : null }, device: { tier: window.PerformanceExperiment?.deviceTier || 'unknown', cores: navigator.hardwareConcurrency || 'unknown', memory: navigator.deviceMemory ? navigator.deviceMemory + 'GB' : 'unknown', userAgent: navigator.userAgent }, rendering: { samples: metrics.renderTime.length, stats: calculateStats(metrics.renderTime, 'duration') }, fps: { samples: metrics.fps.length, stats: calculateStats(metrics.fps, 'fps'), current: metrics.fps.length > 0 ? metrics.fps[metrics.fps.length - 1].fps : null }, memory: performance.memory ? { samples: metrics.memoryUsage.length, current: metrics.memoryUsage.length > 0 ? { used: (metrics.memoryUsage[metrics.memoryUsage.length - 1].used / 1024 / 1024).toFixed(1) + 'MB', total: (metrics.memoryUsage[metrics.memoryUsage.length - 1].total / 1024 / 1024).toFixed(1) + 'MB' } : null, peak: metrics.memoryUsage.length > 0 ? { used: (Math.max.apply(null, metrics.memoryUsage.map(function(m) { return m.used; })) / 1024 / 1024).toFixed(1) + 'MB' } : null } : { available: false }, longTasks: { samples: metrics.longTasks.length, stats: calculateStats(metrics.longTasks, 'duration'), recent: metrics.longTasks.slice(-5) }, dom: { samples: metrics.domNodes.length, current: metrics.domNodes.length > 0 ? metrics.domNodes[metrics.domNodes.length - 1] : null } }; } /** * 导出原始数据(JSON 格式) */ function exportData() { var data = { exportTime: new Date().toISOString(), session: getStats().session, device: getStats().device, metrics: { renderTime: metrics.renderTime, fps: metrics.fps, longTasks: metrics.longTasks, memoryUsage: metrics.memoryUsage, domNodes: metrics.domNodes } }; // 输出到控制台 console.log('[PerfMonitor] 导出数据:'); console.log(JSON.stringify(data, null, 2)); // 返回数据,方便复制 return data; } /** * 启动监控 */ function start() { if (state.isRunning) { console.warn('[PerfMonitor] 监控已在运行中'); return; } console.log('[PerfMonitor] 启动性能监控...'); state.isRunning = true; state.startTime = Date.now(); // 启动各项监控 measureFPS(); observeLongTasks(); // 定期采集内存和 DOM 信息 state.memoryIntervalId = setInterval(function() { measureMemory(); recordDOMNodes(); }, config.memoryCheckInterval); console.log('[PerfMonitor] 监控已启动。使用 PerfMonitor.getStats() 查看实时数据'); } /** * 停止监控 */ function stop() { if (!state.isRunning) { console.warn('[PerfMonitor] 监控未在运行'); return; } console.log('[PerfMonitor] 停止性能监控...'); state.isRunning = false; // 停止各项监控 if (state.fpsAnimationId) { cancelAnimationFrame(state.fpsAnimationId); state.fpsAnimationId = null; } if (state.memoryIntervalId) { clearInterval(state.memoryIntervalId); state.memoryIntervalId = null; } if (state.longTaskObserver) { state.longTaskObserver.disconnect(); state.longTaskObserver = null; } // 显示统计摘要 var stats = getStats(); console.log('[PerfMonitor] 监控已停止。统计摘要:'); console.table({ '监控时长': stats.session.duration, '平均渲染耗时': stats.rendering.stats.avg.toFixed(1) + 'ms', 'P95渲染耗时': stats.rendering.stats.p95.toFixed(1) + 'ms', '平均FPS': stats.fps.stats.avg.toFixed(0), '最低FPS': stats.fps.stats.min, '长任务数量': stats.longTasks.samples }); } /** * 清空数据 */ function clear() { metrics.renderTime = []; metrics.fps = []; metrics.longTasks = []; metrics.memoryUsage = []; metrics.scrollEvents = []; metrics.domNodes = []; console.log('[PerfMonitor] 数据已清空'); } // 公开 API return { start: start, stop: stop, clear: clear, getStats: getStats, export: exportData, recordRender: recordRenderTime, // 用于调试 _metrics: metrics, _state: state, _config: config }; })(); // 自动集成到现有的渲染流程 (function() { // 拦截 ChatbotRenderState,自动记录渲染耗时 if (window.ChatbotRenderState) { var originalRenderState = window.ChatbotRenderState; // 包装 lastRenderDuration 的更新 Object.defineProperty(window.ChatbotRenderState, 'lastRenderDuration', { get: function() { return this._lastRenderDuration || 0; }, set: function(value) { this._lastRenderDuration = value; // 自动记录到性能监控 window.PerfMonitor.recordRender(value, 'chatbot_render'); }, configurable: true }); } })(); console.log('[PerfMonitor] 性能监控工具已加载。使用方法:'); console.log(' PerfMonitor.start() - 启动监控'); console.log(' PerfMonitor.stop() - 停止监控'); console.log(' PerfMonitor.getStats() - 查看统计'); console.log(' PerfMonitor.export() - 导出数据'); console.log(' PerfMonitor.clear() - 清空数据');