UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

417 lines 15.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryManager = void 0; const events_1 = require("events"); class MemoryManager extends events_1.EventEmitter { constructor(config = {}) { super(); this.snapshots = []; this.managedResources = new Map(); this.isRunning = false; this.config = { warningThreshold: 80 * 1024 * 1024, criticalThreshold: 150 * 1024 * 1024, cleanupInterval: 30000, enableAutoCleanup: true, enableGcForcing: false, gcInterval: 60000, enableLeakDetection: true, leakDetectionInterval: 60000, trendSampleCount: 60, ...config, }; const initialSnapshot = this.takeMemorySnapshot(); this.stats = { current: initialSnapshot, peak: initialSnapshot, average: { rss: initialSnapshot.rss, heapUsed: initialSnapshot.heapUsed, heapTotal: initialSnapshot.heapTotal, }, growthRate: 0, cleanupCount: 0, gcCount: 0, leakAlerts: 0, pressureLevel: 'normal', }; this.peakSnapshot = initialSnapshot; } start() { if (this.isRunning) { return; } this.isRunning = true; this.startCleanupMonitoring(); if (this.config.enableGcForcing) { this.startGcMonitoring(); } if (this.config.enableLeakDetection) { this.startLeakDetection(); } this.emit('started', { timestamp: Date.now(), config: this.config, }); } stop() { if (!this.isRunning) { return; } this.isRunning = false; if (this.cleanupTimer) { clearInterval(this.cleanupTimer); delete this.cleanupTimer; } if (this.gcTimer) { clearInterval(this.gcTimer); delete this.gcTimer; } if (this.leakDetectionTimer) { clearInterval(this.leakDetectionTimer); delete this.leakDetectionTimer; } this.emit('stopped', { timestamp: Date.now(), finalStats: this.getStats(), }); } registerResource(resource) { const managedResource = { ...resource, createdAt: Date.now(), lastAccessed: Date.now(), }; this.managedResources.set(resource.id, managedResource); this.emit('resourceRegistered', { resourceId: resource.id, type: resource.type, size: resource.size, timestamp: Date.now(), }); } unregisterResource(resourceId) { const resource = this.managedResources.get(resourceId); if (resource) { this.managedResources.delete(resourceId); this.emit('resourceUnregistered', { resourceId, type: resource.type, size: resource.size, timestamp: Date.now(), }); } } touchResource(resourceId) { const resource = this.managedResources.get(resourceId); if (resource) { resource.lastAccessed = Date.now(); } } async forceCleanup() { const cleanedCount = await this.performCleanup(); this.emit('cleanupForced', { resourcesCleaned: cleanedCount, timestamp: Date.now(), }); return cleanedCount; } forceGarbageCollection() { if (global.gc) { try { global.gc(); this.stats.gcCount++; this.emit('gcForced', { timestamp: Date.now(), }); } catch (error) { this.emit('gcError', { error, timestamp: Date.now(), }); } } } getStats() { this.updateStats(); return { ...this.stats }; } getSnapshots(count) { const snapshots = count ? this.snapshots.slice(-count) : this.snapshots; return [...snapshots]; } getManagedResources() { const now = Date.now(); return Array.from(this.managedResources.values()).map(resource => ({ id: resource.id, type: resource.type, size: resource.size, age: now - resource.createdAt, lastAccessed: resource.lastAccessed, priority: resource.priority, })); } detectMemoryLeaks() { if (this.snapshots.length < 5) { return { leakDetected: false, severity: 'low', growthTrend: 'stable', growthRatePerMinute: 0, confidence: 0.1, recommendations: ['Insufficient data for leak detection'], }; } const recentSnapshots = this.snapshots.slice(-10); const growthRates = this.calculateGrowthRates(recentSnapshots); const avgGrowthRate = growthRates.reduce((sum, rate) => sum + rate, 0) / growthRates.length; const growthRatePerMinute = avgGrowthRate * 60; let growthTrend = 'stable'; if (growthRatePerMinute > 5 * 1024 * 1024) { growthTrend = 'rapidly_growing'; } else if (growthRatePerMinute > 1 * 1024 * 1024) { growthTrend = 'growing'; } const growthConsistency = this.calculateGrowthConsistency(growthRates); const confidence = Math.min(growthConsistency, 1.0); const leakDetected = growthTrend !== 'stable' && confidence > 0.7; let severity = 'low'; if (growthRatePerMinute > 10 * 1024 * 1024) { severity = 'high'; } else if (growthRatePerMinute > 3 * 1024 * 1024) { severity = 'medium'; } const recommendations = this.generateLeakRecommendations(growthTrend, severity); return { leakDetected, severity, growthTrend, growthRatePerMinute, confidence, recommendations, }; } setPressureLevel(level) { this.stats.pressureLevel = level; this.emit('pressureLevelChanged', { level, timestamp: Date.now(), }); if (level === 'critical') { this.handleCriticalMemoryPressure(); } } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; this.emit('configUpdated', { config: this.config, timestamp: Date.now(), }); } takeMemorySnapshot() { const memUsage = process.memoryUsage(); const timestamp = Date.now(); const usagePercentage = (memUsage.heapUsed / memUsage.heapTotal) * 100; return { timestamp, rss: memUsage.rss, heapUsed: memUsage.heapUsed, heapTotal: memUsage.heapTotal, external: memUsage.external, arrayBuffers: memUsage.arrayBuffers || 0, usagePercentage, }; } updateStats() { const currentSnapshot = this.takeMemorySnapshot(); this.stats.current = currentSnapshot; this.snapshots.push(currentSnapshot); if (this.snapshots.length > this.config.trendSampleCount) { this.snapshots.shift(); } if (!this.peakSnapshot || currentSnapshot.heapUsed > this.peakSnapshot.heapUsed) { this.peakSnapshot = currentSnapshot; this.stats.peak = currentSnapshot; } if (this.snapshots.length > 1) { this.stats.average = { rss: this.snapshots.reduce((sum, s) => sum + s.rss, 0) / this.snapshots.length, heapUsed: this.snapshots.reduce((sum, s) => sum + s.heapUsed, 0) / this.snapshots.length, heapTotal: this.snapshots.reduce((sum, s) => sum + s.heapTotal, 0) / this.snapshots.length, }; const recentSnapshots = this.snapshots.slice(-5); if (recentSnapshots.length > 1) { const growthRates = this.calculateGrowthRates(recentSnapshots); this.stats.growthRate = growthRates.reduce((sum, rate) => sum + rate, 0) / growthRates.length; } } this.updatePressureLevel(currentSnapshot); } updatePressureLevel(snapshot) { const oldLevel = this.stats.pressureLevel; if (snapshot.heapUsed > this.config.criticalThreshold) { this.stats.pressureLevel = 'critical'; } else if (snapshot.heapUsed > this.config.warningThreshold) { this.stats.pressureLevel = 'warning'; } else { this.stats.pressureLevel = 'normal'; } if (oldLevel !== this.stats.pressureLevel) { this.emit('pressureLevelChanged', { level: this.stats.pressureLevel, previousLevel: oldLevel, snapshot, timestamp: Date.now(), }); if (this.stats.pressureLevel === 'critical') { this.handleCriticalMemoryPressure(); } } } async handleCriticalMemoryPressure() { this.emit('criticalMemoryPressure', { snapshot: this.stats.current, timestamp: Date.now(), }); const cleanedResources = await this.performCleanup(true); if (this.config.enableGcForcing) { this.forceGarbageCollection(); } this.emit('criticalMemoryHandled', { cleanedResources, timestamp: Date.now(), }); } startCleanupMonitoring() { this.cleanupTimer = setInterval(() => { if (this.isRunning && this.config.enableAutoCleanup) { this.performCleanup(); } this.updateStats(); }, this.config.cleanupInterval); } startGcMonitoring() { this.gcTimer = setInterval(() => { if (this.isRunning && this.stats.pressureLevel !== 'normal') { this.forceGarbageCollection(); } }, this.config.gcInterval); } startLeakDetection() { this.leakDetectionTimer = setInterval(() => { if (this.isRunning) { const leakDetection = this.detectMemoryLeaks(); if (leakDetection.leakDetected) { this.stats.leakAlerts++; this.emit('memoryLeakDetected', { detection: leakDetection, timestamp: Date.now(), }); } } }, this.config.leakDetectionInterval); } async performCleanup(aggressive = false) { const now = Date.now(); const cleanupThreshold = aggressive ? 10000 : 300000; let cleanedCount = 0; const resourcesToCleanup = []; for (const resource of this.managedResources.values()) { if (resource.disposable) { const age = now - resource.lastAccessed; const shouldCleanup = age > cleanupThreshold || (aggressive && resource.priority === 'low'); if (shouldCleanup) { resourcesToCleanup.push(resource); } } } resourcesToCleanup.sort((a, b) => { const priorityOrder = { low: 0, medium: 1, high: 2 }; return priorityOrder[a.priority] - priorityOrder[b.priority]; }); for (const resource of resourcesToCleanup) { try { resource.cleanup(); this.managedResources.delete(resource.id); cleanedCount++; this.emit('resourceCleaned', { resourceId: resource.id, type: resource.type, size: resource.size, age: now - resource.createdAt, timestamp: now, }); } catch (error) { this.emit('cleanupError', { resourceId: resource.id, error, timestamp: now, }); } } if (cleanedCount > 0) { this.stats.cleanupCount++; this.emit('cleanupCompleted', { resourcesCleaned: cleanedCount, aggressive, timestamp: now, }); } return cleanedCount; } calculateGrowthRates(snapshots) { const growthRates = []; for (let i = 1; i < snapshots.length; i++) { const current = snapshots[i]; const previous = snapshots[i - 1]; if (!current || !previous) continue; const timeDiff = (current.timestamp - previous.timestamp) / 1000; const memoryDiff = current.heapUsed - previous.heapUsed; if (timeDiff > 0) { growthRates.push(memoryDiff / timeDiff); } } return growthRates; } calculateGrowthConsistency(growthRates) { if (growthRates.length < 2) { return 0; } const mean = growthRates.reduce((sum, rate) => sum + rate, 0) / growthRates.length; const variance = growthRates.reduce((sum, rate) => sum + Math.pow(rate - mean, 2), 0) / growthRates.length; const standardDeviation = Math.sqrt(variance); const consistency = 1 / (1 + standardDeviation / (Math.abs(mean) + 1)); return consistency; } generateLeakRecommendations(growthTrend, severity) { const recommendations = []; if (growthTrend === 'rapidly_growing') { recommendations.push('Immediate action required: Memory is growing rapidly'); recommendations.push('Check for unbounded arrays, maps, or event listeners'); recommendations.push('Review recent code changes for potential leaks'); } if (severity === 'high') { recommendations.push('Consider forcing garbage collection'); recommendations.push('Enable aggressive resource cleanup'); recommendations.push('Monitor component lifecycle for proper cleanup'); } if (growthTrend === 'growing') { recommendations.push('Monitor memory usage closely'); recommendations.push('Review data structures for potential optimization'); recommendations.push('Check for circular references'); } recommendations.push('Use memory profiling tools for detailed analysis'); recommendations.push('Implement resource pooling where appropriate'); return recommendations; } } exports.MemoryManager = MemoryManager; //# sourceMappingURL=memory-manager.js.map