topfans/frontend/utils/performance-monitor.js
2026-04-07 23:08:49 +08:00

369 lines
9.7 KiB
JavaScript

/**
* 性能监控模块
* 用于追踪和记录页面性能指标
*/
import { MONITOR_CONFIG } from './performance-config'
class PerformanceMonitor {
constructor() {
this.metrics = {
firstScreenLoadTime: 0,
imageLoadStats: {
total: 0,
success: 0,
failed: 0,
totalTime: 0
},
pollingStats: {
totalRequests: 0,
successRequests: 0,
failedRequests: 0,
totalResponseTime: 0
},
animationStats: {
fps: 0,
frameCount: 0
}
}
this.startTime = 0
this.fpsTimer = null
this.lastFrameTime = 0
this.isRunning = false
}
/**
* 检查监控是否启用
* @returns {boolean}
*/
isEnabled() {
// 如果设置了强制开关,使用强制开关的值
if (MONITOR_CONFIG.FORCE_ENABLE !== null) {
return MONITOR_CONFIG.FORCE_ENABLE
}
// 否则使用默认的 ENABLED 配置
return MONITOR_CONFIG.ENABLED
}
/**
* 开始监控
*/
start() {
if (!this.isEnabled()) {
console.log('[性能监控] 监控已禁用')
return
}
if (this.isRunning) {
console.warn('[性能监控] 监控已在运行中')
return
}
this.isRunning = true
this.startTime = Date.now()
console.log('[性能监控] 开始监控')
if (MONITOR_CONFIG.TRACK_FPS) {
this.startFPSTracking()
}
}
/**
* 停止监控
*/
stop() {
if (!this.isRunning) {
return
}
if (this.fpsTimer) {
// uni-app 兼容的取消动画帧
// #ifdef H5
cancelAnimationFrame(this.fpsTimer)
// #endif
// #ifndef H5
clearTimeout(this.fpsTimer)
// #endif
this.fpsTimer = null
}
this.isRunning = false
console.log('[性能监控] 停止监控')
// 如果配置了自动打印报告,则打印
if (MONITOR_CONFIG.AUTO_PRINT_REPORT && this.isEnabled()) {
this.printReport()
}
}
/**
* 记录首屏加载时间
*/
recordFirstScreenLoad() {
if (!this.isEnabled() || !this.isRunning) return
this.metrics.firstScreenLoadTime = Date.now() - this.startTime
this.log('info', `首屏加载时间: ${this.metrics.firstScreenLoadTime}ms`)
// 如果加载时间过长,发出警告
if (this.metrics.firstScreenLoadTime > 3000) {
this.log('warn', `首屏加载时间过长: ${this.metrics.firstScreenLoadTime}ms`)
}
}
/**
* 记录图片加载
* @param {boolean} success - 是否成功
* @param {number} loadTime - 加载时间(毫秒)
*/
recordImageLoad(success, loadTime = 0) {
if (!this.isEnabled() || !this.isRunning) return
this.metrics.imageLoadStats.total++
if (success) {
this.metrics.imageLoadStats.success++
this.metrics.imageLoadStats.totalTime += loadTime
} else {
this.metrics.imageLoadStats.failed++
}
// 计算成功率
const successRate = (this.metrics.imageLoadStats.success / this.metrics.imageLoadStats.total * 100).toFixed(2)
const avgLoadTime = this.metrics.imageLoadStats.success > 0
? (this.metrics.imageLoadStats.totalTime / this.metrics.imageLoadStats.success).toFixed(0)
: 0
this.log('debug', `图片加载统计 - 成功率: ${successRate}%, 平均加载时间: ${avgLoadTime}ms`)
// 如果失败率过高,发出警告
if (this.metrics.imageLoadStats.failed / this.metrics.imageLoadStats.total > 0.3) {
this.log('warn', `图片加载失败率过高: ${(this.metrics.imageLoadStats.failed / this.metrics.imageLoadStats.total * 100).toFixed(2)}%`)
}
}
/**
* 记录轮询请求
* @param {boolean} success - 是否成功
* @param {number} responseTime - 响应时间(毫秒)
*/
recordPollingRequest(success, responseTime = 0) {
if (!this.isEnabled() || !this.isRunning) return
this.metrics.pollingStats.totalRequests++
if (success) {
this.metrics.pollingStats.successRequests++
this.metrics.pollingStats.totalResponseTime += responseTime
} else {
this.metrics.pollingStats.failedRequests++
}
// 计算平均响应时间
const avgResponseTime = this.metrics.pollingStats.successRequests > 0
? (this.metrics.pollingStats.totalResponseTime / this.metrics.pollingStats.successRequests).toFixed(0)
: 0
this.log('debug', `轮询统计 - 平均响应时间: ${avgResponseTime}ms`)
// 如果响应时间过长,发出警告
if (responseTime > 5000) {
this.log('warn', `轮询响应时间过长: ${responseTime}ms`)
}
}
/**
* 开始追踪 FPS
*/
startFPSTracking() {
if (!this.isEnabled()) return
let frameCount = 0
let lastTime = Date.now()
const trackFrame = () => {
frameCount++
const currentTime = Date.now()
// 每秒计算一次 FPS
if (currentTime - lastTime >= 1000) {
this.metrics.animationStats.fps = frameCount
this.metrics.animationStats.frameCount += frameCount
this.log('debug', `当前 FPS: ${frameCount}`)
// 如果 FPS 过低,发出警告
if (frameCount < 30) {
this.log('warn', `FPS 过低: ${frameCount}`)
}
frameCount = 0
lastTime = currentTime
}
if (this.isRunning) {
// uni-app 兼容的动画帧请求
// #ifdef H5
this.fpsTimer = requestAnimationFrame(trackFrame)
// #endif
// #ifndef H5
this.fpsTimer = setTimeout(trackFrame, 16) // 约 60fps
// #endif
}
}
trackFrame()
}
/**
* 获取性能报告
* @returns {Object} 性能指标对象
*/
getReport() {
const report = {
...this.metrics,
imageLoadSuccessRate: this.metrics.imageLoadStats.total > 0
? (this.metrics.imageLoadStats.success / this.metrics.imageLoadStats.total * 100).toFixed(2) + '%'
: '0%',
avgImageLoadTime: this.metrics.imageLoadStats.success > 0
? (this.metrics.imageLoadStats.totalTime / this.metrics.imageLoadStats.success).toFixed(0) + 'ms'
: '0ms',
pollingSuccessRate: this.metrics.pollingStats.totalRequests > 0
? (this.metrics.pollingStats.successRequests / this.metrics.pollingStats.totalRequests * 100).toFixed(2) + '%'
: '0%',
avgPollingResponseTime: this.metrics.pollingStats.successRequests > 0
? (this.metrics.pollingStats.totalResponseTime / this.metrics.pollingStats.successRequests).toFixed(0) + 'ms'
: '0ms'
}
return report
}
/**
* 打印性能报告
*/
printReport() {
if (!this.isEnabled()) return
const report = this.getReport()
console.log('========== 性能监控报告 ==========')
console.log(`首屏加载时间: ${report.firstScreenLoadTime}ms`)
console.log(`图片加载统计:`)
console.log(` - 总数: ${report.imageLoadStats.total}`)
console.log(` - 成功: ${report.imageLoadStats.success}`)
console.log(` - 失败: ${report.imageLoadStats.failed}`)
console.log(` - 成功率: ${report.imageLoadSuccessRate}`)
console.log(` - 平均加载时间: ${report.avgImageLoadTime}`)
console.log(`轮询统计:`)
console.log(` - 总请求数: ${report.pollingStats.totalRequests}`)
console.log(` - 成功: ${report.pollingStats.successRequests}`)
console.log(` - 失败: ${report.pollingStats.failedRequests}`)
console.log(` - 成功率: ${report.pollingSuccessRate}`)
console.log(` - 平均响应时间: ${report.avgPollingResponseTime}`)
if (MONITOR_CONFIG.TRACK_FPS) {
console.log(`动画统计:`)
console.log(` - 当前 FPS: ${report.animationStats.fps}`)
console.log(` - 总帧数: ${report.animationStats.frameCount}`)
}
console.log('==================================')
}
/**
* 重置统计数据
*/
reset() {
this.metrics = {
firstScreenLoadTime: 0,
imageLoadStats: {
total: 0,
success: 0,
failed: 0,
totalTime: 0
},
pollingStats: {
totalRequests: 0,
successRequests: 0,
failedRequests: 0,
totalResponseTime: 0
},
animationStats: {
fps: 0,
frameCount: 0
}
}
this.startTime = Date.now()
console.log('[性能监控] 统计数据已重置')
}
/**
* 启用监控(运行时控制)
*/
enable() {
MONITOR_CONFIG.FORCE_ENABLE = true
console.log('[性能监控] 已手动启用')
if (!this.isRunning) {
this.start()
}
}
/**
* 禁用监控(运行时控制)
*/
disable() {
MONITOR_CONFIG.FORCE_ENABLE = false
console.log('[性能监控] 已手动禁用')
if (this.isRunning) {
this.stop()
}
}
/**
* 日志输出
* @param {string} level - 日志级别
* @param {string} message - 日志消息
*/
log(level, message) {
if (!this.isEnabled()) return
const levels = ['debug', 'info', 'warn', 'error']
const configLevel = MONITOR_CONFIG.LOG_LEVEL
if (levels.indexOf(level) >= levels.indexOf(configLevel)) {
const prefix = '[性能监控]'
switch (level) {
case 'debug':
console.log(prefix, message)
break
case 'info':
console.info(prefix, message)
break
case 'warn':
console.warn(prefix, message)
break
case 'error':
console.error(prefix, message)
break
}
}
}
}
// 创建单例实例
const performanceMonitor = new PerformanceMonitor()
export default performanceMonitor
// 导出常用方法
export const {
start,
stop,
recordFirstScreenLoad,
recordImageLoad,
recordPollingRequest,
getReport,
printReport,
reset,
enable,
disable,
isEnabled
} = performanceMonitor