paper-burner/js/chatbot/utils/performance-monitor.js

403 lines
11 KiB
JavaScript
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.

/**
* 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() - 清空数据');