| |
| |
| |
| |
|
|
| 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() |
|
|