|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
forceCleanupInterval: 30 * 60 * 1000, |
|
|
memoryThreshold: 100 * 1024 * 1024, |
|
|
sensitiveDataPatterns: [/password/i, /token/i, /secret/i, /key/i, /credential/i] |
|
|
} |
|
|
|
|
|
|
|
|
this.setupSecurityCleanup() |
|
|
|
|
|
|
|
|
this.setupPeriodicReporting() |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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() { |
|
|
|
|
|
setInterval( |
|
|
() => { |
|
|
this.performSecurityCleanup() |
|
|
}, |
|
|
10 * 60 * 1000 |
|
|
) |
|
|
|
|
|
|
|
|
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() { |
|
|
|
|
|
setInterval( |
|
|
() => { |
|
|
const stats = this.getGlobalStats() |
|
|
logger.info( |
|
|
`๐ Quick Stats - Caches: ${stats.cacheCount}, Size: ${stats.totalSize}, Hit Rate: ${stats.averageHitRate}` |
|
|
) |
|
|
}, |
|
|
5 * 60 * 1000 |
|
|
) |
|
|
|
|
|
|
|
|
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` |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
totalBytes += key.length * 2 |
|
|
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 |
|
|
|
|
|
|
|
|
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() |
|
|
|