cc / src /utils /cacheMonitor.js
hequ's picture
Upload 221 files
69b897d verified
/**
* ็ผ“ๅญ˜็›‘ๆŽงๅ’Œ็ฎก็†ๅทฅๅ…ท
* ๆไพ›็ปŸไธ€็š„็ผ“ๅญ˜็›‘ๆŽงใ€็ปŸ่ฎกๅ’Œๅฎ‰ๅ…จๆธ…็†ๅŠŸ่ƒฝ
*/
const logger = require('./logger')
const crypto = require('crypto')
class CacheMonitor {
constructor() {
this.monitors = new Map() // ๅญ˜ๅ‚จๆ‰€ๆœ‰่ขซ็›‘ๆŽง็š„็ผ“ๅญ˜ๅฎžไพ‹
this.startTime = Date.now()
this.totalHits = 0
this.totalMisses = 0
this.totalEvictions = 0
// ๐Ÿ”’ ๅฎ‰ๅ…จ้…็ฝฎ
this.securityConfig = {
maxCacheAge: 15 * 60 * 1000, // ๆœ€ๅคง็ผ“ๅญ˜ๅนด้พ„ 15 ๅˆ†้’Ÿ
forceCleanupInterval: 30 * 60 * 1000, // ๅผบๅˆถๆธ…็†้—ด้š” 30 ๅˆ†้’Ÿ
memoryThreshold: 100 * 1024 * 1024, // ๅ†…ๅญ˜้˜ˆๅ€ผ 100MB
sensitiveDataPatterns: [/password/i, /token/i, /secret/i, /key/i, /credential/i]
}
// ๐Ÿงน ๅฎšๆœŸๆ‰ง่กŒๅฎ‰ๅ…จๆธ…็†
this.setupSecurityCleanup()
// ๐Ÿ“Š ๅฎšๆœŸๆŠฅๅ‘Š็ปŸ่ฎกไฟกๆฏ
this.setupPeriodicReporting()
}
/**
* ๆณจๅ†Œ็ผ“ๅญ˜ๅฎžไพ‹่ฟ›่กŒ็›‘ๆŽง
* @param {string} name - ็ผ“ๅญ˜ๅ็งฐ
* @param {LRUCache} cache - ็ผ“ๅญ˜ๅฎžไพ‹
*/
registerCache(name, cache) {
if (this.monitors.has(name)) {
logger.warn(`โš ๏ธ Cache ${name} is already registered, updating reference`)
}
this.monitors.set(name, {
cache,
registeredAt: Date.now(),
lastCleanup: Date.now(),
totalCleanups: 0
})
logger.info(`๐Ÿ“ฆ Registered cache for monitoring: ${name}`)
}
/**
* ่Žทๅ–ๆ‰€ๆœ‰็ผ“ๅญ˜็š„็ปผๅˆ็ปŸ่ฎก
*/
getGlobalStats() {
const stats = {
uptime: Math.floor((Date.now() - this.startTime) / 1000), // ็ง’
cacheCount: this.monitors.size,
totalSize: 0,
totalHits: 0,
totalMisses: 0,
totalEvictions: 0,
averageHitRate: 0,
caches: {}
}
for (const [name, monitor] of this.monitors) {
const cacheStats = monitor.cache.getStats()
stats.totalSize += cacheStats.size
stats.totalHits += cacheStats.hits
stats.totalMisses += cacheStats.misses
stats.totalEvictions += cacheStats.evictions
stats.caches[name] = {
...cacheStats,
lastCleanup: new Date(monitor.lastCleanup).toISOString(),
totalCleanups: monitor.totalCleanups,
age: Math.floor((Date.now() - monitor.registeredAt) / 1000) // ็ง’
}
}
const totalRequests = stats.totalHits + stats.totalMisses
stats.averageHitRate =
totalRequests > 0 ? `${((stats.totalHits / totalRequests) * 100).toFixed(2)}%` : '0%'
return stats
}
/**
* ๐Ÿ”’ ๆ‰ง่กŒๅฎ‰ๅ…จๆธ…็†
* ๆธ…็†่ฟ‡ๆœŸๆ•ฐๆฎๅ’Œๆฝœๅœจ็š„ๆ•ๆ„Ÿไฟกๆฏ
*/
performSecurityCleanup() {
logger.info('๐Ÿ”’ Starting security cleanup for all caches')
for (const [name, monitor] of this.monitors) {
try {
const { cache } = monitor
const beforeSize = cache.cache.size
// ๆ‰ง่กŒๅธธ่ง„ๆธ…็†
cache.cleanup()
// ๆฃ€ๆŸฅ็ผ“ๅญ˜ๅนด้พ„๏ผŒๅฆ‚ๆžœๅคช่€ๅˆ™ๅฎŒๅ…จๆธ…็ฉบ
const cacheAge = Date.now() - monitor.registeredAt
if (cacheAge > this.securityConfig.maxCacheAge * 2) {
logger.warn(
`โš ๏ธ Cache ${name} is too old (${Math.floor(cacheAge / 60000)}min), performing full clear`
)
cache.clear()
}
monitor.lastCleanup = Date.now()
monitor.totalCleanups++
const afterSize = cache.cache.size
if (beforeSize !== afterSize) {
logger.info(`๐Ÿงน Cache ${name}: Cleaned ${beforeSize - afterSize} items`)
}
} catch (error) {
logger.error(`โŒ Error cleaning cache ${name}:`, error)
}
}
}
/**
* ๐Ÿ“Š ็”Ÿๆˆ่ฏฆ็ป†ๆŠฅๅ‘Š
*/
generateReport() {
const stats = this.getGlobalStats()
logger.info('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•')
logger.info('๐Ÿ“Š Cache System Performance Report')
logger.info('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•')
logger.info(`โฑ๏ธ Uptime: ${this.formatUptime(stats.uptime)}`)
logger.info(`๐Ÿ“ฆ Active Caches: ${stats.cacheCount}`)
logger.info(`๐Ÿ“ˆ Total Cache Size: ${stats.totalSize} items`)
logger.info(`๐ŸŽฏ Global Hit Rate: ${stats.averageHitRate}`)
logger.info(`โœ… Total Hits: ${stats.totalHits.toLocaleString()}`)
logger.info(`โŒ Total Misses: ${stats.totalMisses.toLocaleString()}`)
logger.info(`๐Ÿ—‘๏ธ Total Evictions: ${stats.totalEvictions.toLocaleString()}`)
logger.info('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€')
// ่ฏฆ็ป†็š„ๆฏไธช็ผ“ๅญ˜็ปŸ่ฎก
for (const [name, cacheStats] of Object.entries(stats.caches)) {
logger.info(`\n๐Ÿ“ฆ ${name}:`)
logger.info(
` Size: ${cacheStats.size}/${cacheStats.maxSize} | Hit Rate: ${cacheStats.hitRate}`
)
logger.info(
` Hits: ${cacheStats.hits} | Misses: ${cacheStats.misses} | Evictions: ${cacheStats.evictions}`
)
logger.info(
` Age: ${this.formatUptime(cacheStats.age)} | Cleanups: ${cacheStats.totalCleanups}`
)
}
logger.info('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•')
}
/**
* ๐Ÿงน ่ฎพ็ฝฎๅฎšๆœŸๅฎ‰ๅ…จๆธ…็†
*/
setupSecurityCleanup() {
// ๆฏ 10 ๅˆ†้’Ÿๆ‰ง่กŒไธ€ๆฌกๅฎ‰ๅ…จๆธ…็†
setInterval(
() => {
this.performSecurityCleanup()
},
10 * 60 * 1000
)
// ๆฏ 30 ๅˆ†้’ŸๅผบๅˆถๅฎŒๆ•ดๆธ…็†
setInterval(() => {
logger.warn('โš ๏ธ Performing forced complete cleanup for security')
for (const [name, monitor] of this.monitors) {
monitor.cache.clear()
logger.info(`๐Ÿ—‘๏ธ Force cleared cache: ${name}`)
}
}, this.securityConfig.forceCleanupInterval)
}
/**
* ๐Ÿ“Š ่ฎพ็ฝฎๅฎšๆœŸๆŠฅๅ‘Š
*/
setupPeriodicReporting() {
// ๆฏ 5 ๅˆ†้’Ÿ็”Ÿๆˆไธ€ๆฌก็ฎ€ๅ•็ปŸ่ฎก
setInterval(
() => {
const stats = this.getGlobalStats()
logger.info(
`๐Ÿ“Š Quick Stats - Caches: ${stats.cacheCount}, Size: ${stats.totalSize}, Hit Rate: ${stats.averageHitRate}`
)
},
5 * 60 * 1000
)
// ๆฏ 30 ๅˆ†้’Ÿ็”Ÿๆˆไธ€ๆฌก่ฏฆ็ป†ๆŠฅๅ‘Š
setInterval(
() => {
this.generateReport()
},
30 * 60 * 1000
)
}
/**
* ๆ ผๅผๅŒ–่ฟ่กŒๆ—ถ้—ด
*/
formatUptime(seconds) {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
if (hours > 0) {
return `${hours}h ${minutes}m ${secs}s`
} else if (minutes > 0) {
return `${minutes}m ${secs}s`
} else {
return `${secs}s`
}
}
/**
* ๐Ÿ” ็”Ÿๆˆๅฎ‰ๅ…จ็š„็ผ“ๅญ˜้”ฎ
* ไฝฟ็”จ SHA-256 ๅ“ˆๅธŒ้ฟๅ…ๆšด้œฒๅŽŸๅง‹ๆ•ฐๆฎ
*/
static generateSecureCacheKey(data) {
return crypto.createHash('sha256').update(data).digest('hex')
}
/**
* ๐Ÿ›ก๏ธ ้ชŒ่ฏ็ผ“ๅญ˜ๆ•ฐๆฎๅฎ‰ๅ…จๆ€ง
* ๆฃ€ๆŸฅๆ˜ฏๅฆๅŒ…ๅซๆ•ๆ„Ÿไฟกๆฏ
*/
validateCacheSecurity(data) {
const dataStr = typeof data === 'string' ? data : JSON.stringify(data)
for (const pattern of this.securityConfig.sensitiveDataPatterns) {
if (pattern.test(dataStr)) {
logger.warn('โš ๏ธ Potential sensitive data detected in cache')
return false
}
}
return true
}
/**
* ๐Ÿ’พ ่Žทๅ–ๅ†…ๅญ˜ไฝฟ็”จไผฐ็ฎ—
*/
estimateMemoryUsage() {
let totalBytes = 0
for (const [, monitor] of this.monitors) {
const { cache } = monitor.cache
for (const [key, item] of cache) {
// ็ฒ—็•ฅไผฐ็ฎ—๏ผškey ้•ฟๅบฆ + value ๅบๅˆ—ๅŒ–้•ฟๅบฆ
totalBytes += key.length * 2 // UTF-16
totalBytes += JSON.stringify(item).length * 2
}
}
return {
bytes: totalBytes,
mb: (totalBytes / (1024 * 1024)).toFixed(2),
warning: totalBytes > this.securityConfig.memoryThreshold
}
}
/**
* ๐Ÿšจ ็ดงๆ€ฅๆธ…็†
* ๅœจๅ†…ๅญ˜ๅŽ‹ๅŠ›ๅคงๆ—ถไฝฟ็”จ
*/
emergencyCleanup() {
logger.error('๐Ÿšจ EMERGENCY CLEANUP INITIATED')
for (const [name, monitor] of this.monitors) {
const { cache } = monitor
const beforeSize = cache.cache.size
// ๆธ…็†ไธ€ๅŠ็š„็ผ“ๅญ˜้กน๏ผˆLRU ไผšไฟ็•™ๆœ€่ฟ‘ไฝฟ็”จ็š„๏ผ‰
const targetSize = Math.floor(cache.maxSize / 2)
while (cache.cache.size > targetSize) {
const firstKey = cache.cache.keys().next().value
cache.cache.delete(firstKey)
}
logger.warn(`๐Ÿšจ Emergency cleaned ${name}: ${beforeSize} -> ${cache.cache.size} items`)
}
}
}
// ๅฏผๅ‡บๅ•ไพ‹
module.exports = new CacheMonitor()