UNPKG

@ahmedhegazee/nestjs-telescope

Version:

Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling

559 lines 22.2 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var MemoryOptimizerService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryOptimizerService = void 0; const common_1 = require("@nestjs/common"); const rxjs_1 = require("rxjs"); const common_2 = require("@nestjs/common"); let MemoryOptimizerService = MemoryOptimizerService_1 = class MemoryOptimizerService { constructor(telescopeConfig) { this.telescopeConfig = telescopeConfig; this.logger = new common_1.Logger(MemoryOptimizerService_1.name); this.memoryHistory = []; this.memoryLeaks = new Map(); this.optimizations = []; this.metricsSubject = new rxjs_1.Subject(); this.healthSubject = new rxjs_1.Subject(); this.leakSubject = new rxjs_1.Subject(); this.optimizationSubject = new rxjs_1.Subject(); this.monitoringInterval = null; this.optimizationInterval = null; this.gcCount = 0; this.lastGcTime = 0; this.config = this.telescopeConfig.memory || this.getDefaultMemoryConfig(); } async onModuleInit() { if (!this.config.enabled) { this.logger.log('Memory optimization disabled'); return; } await this.initializeOptimizer(); this.startMonitoring(); this.startOptimization(); this.setupGcMonitoring(); this.logger.log('Memory optimizer service initialized'); } getDefaultMemoryConfig() { return { enabled: true, monitoring: { enabled: true, interval: 30000, threshold: { heapUsed: 80, heapTotal: 90, external: 70, rss: 85, }, }, optimization: { autoGc: true, gcThreshold: 75, leakDetection: true, memoryPooling: true, compression: true, }, alerts: { enabled: true, criticalThreshold: 90, warningThreshold: 75, }, }; } async initializeOptimizer() { if (this.config.optimization.memoryPooling) { await this.initializeMemoryPools(); } if (this.config.optimization.leakDetection) { await this.initializeLeakDetection(); } const initialMetrics = this.getCurrentMemoryMetrics(); this.memoryHistory.push(initialMetrics); } async initializeMemoryPools() { this.logger.log('Initializing memory pools'); } async initializeLeakDetection() { this.logger.log('Initializing memory leak detection'); } startMonitoring() { if (!this.config.monitoring.enabled) return; this.monitoringInterval = (0, rxjs_1.interval)(this.config.monitoring.interval).subscribe(async () => { const metrics = this.getCurrentMemoryMetrics(); this.memoryHistory.push(metrics); this.metricsSubject.next(metrics); if (this.memoryHistory.length > 1000) { this.memoryHistory.splice(0, this.memoryHistory.length - 1000); } await this.checkMemoryHealth(metrics); if (this.config.optimization.leakDetection) { await this.detectMemoryLeaks(metrics); } }); } startOptimization() { this.optimizationInterval = (0, rxjs_1.interval)(120000).subscribe(async () => { await this.runOptimizationCycle(); }); } setupGcMonitoring() { if (typeof global.gc === 'function') { const originalGc = global.gc; global.gc = (...args) => { const startTime = Date.now(); const result = originalGc.apply(global, args); const duration = Date.now() - startTime; this.gcCount++; this.lastGcTime = Date.now(); this.logger.debug(`Garbage collection completed in ${duration}ms`); return result; }; } } getCurrentMemoryMetrics() { const memUsage = process.memoryUsage(); const gcStats = this.getGcStats(); return { timestamp: new Date(), heapUsed: memUsage.heapUsed, heapTotal: memUsage.heapTotal, heapFree: memUsage.heapTotal - memUsage.heapUsed, external: memUsage.external, rss: memUsage.rss, arrayBuffers: memUsage.arrayBuffers, heapUsedPercentage: (memUsage.heapUsed / memUsage.heapTotal) * 100, heapTotalPercentage: (memUsage.heapTotal / memUsage.rss) * 100, externalPercentage: (memUsage.external / memUsage.rss) * 100, rssPercentage: (memUsage.rss / (1024 * 1024 * 1024)) * 100, gc: gcStats, }; } getGcStats() { return { count: this.gcCount, duration: Date.now() - this.lastGcTime, type: 'mark-and-sweep', }; } async checkMemoryHealth(metrics) { const health = this.calculateMemoryHealth(metrics); this.healthSubject.next(health); if (this.config.alerts.enabled) { await this.checkMemoryAlerts(metrics); } if (this.config.optimization.autoGc && metrics.heapUsedPercentage > this.config.optimization.gcThreshold) { await this.triggerGarbageCollection(); } } calculateMemoryHealth(metrics) { const issues = []; const recommendations = []; let score = 100; if (metrics.heapUsedPercentage > this.config.monitoring.threshold.heapUsed) { issues.push(`High heap usage: ${metrics.heapUsedPercentage.toFixed(1)}%`); score -= 20; recommendations.push('Consider garbage collection or memory cleanup'); } if (metrics.externalPercentage > this.config.monitoring.threshold.external) { issues.push(`High external memory usage: ${metrics.externalPercentage.toFixed(1)}%`); score -= 15; recommendations.push('Check for memory leaks in external dependencies'); } if (metrics.rssPercentage > this.config.monitoring.threshold.rss) { issues.push(`High RSS usage: ${metrics.rssPercentage.toFixed(1)}%`); score -= 10; recommendations.push('Consider process restart or memory optimization'); } const growthTrend = this.calculateMemoryGrowthTrend(); if (growthTrend > 10) { issues.push(`Memory growing rapidly: ${growthTrend.toFixed(1)}% per minute`); score -= 25; recommendations.push('Investigate for memory leaks'); } let status = 'healthy'; if (score < 50) status = 'critical'; else if (score < 75) status = 'warning'; return { status, score: Math.max(0, score), issues, recommendations, metrics, }; } calculateMemoryGrowthTrend() { if (this.memoryHistory.length < 10) return 0; const recent = this.memoryHistory.slice(-5); const older = this.memoryHistory.slice(-10, -5); const recentAvg = recent.reduce((sum, m) => sum + m.heapUsed, 0) / recent.length; const olderAvg = older.reduce((sum, m) => sum + m.heapUsed, 0) / older.length; if (olderAvg === 0) return 0; return ((recentAvg - olderAvg) / olderAvg) * 100; } async checkMemoryAlerts(metrics) { if (metrics.heapUsedPercentage > this.config.alerts.criticalThreshold) { this.logger.error(`CRITICAL: Memory usage at ${metrics.heapUsedPercentage.toFixed(1)}%`); } else if (metrics.heapUsedPercentage > this.config.alerts.warningThreshold) { this.logger.warn(`WARNING: Memory usage at ${metrics.heapUsedPercentage.toFixed(1)}%`); } } async detectMemoryLeaks(metrics) { const growthPattern = this.analyzeMemoryGrowthPattern(); for (const pattern of growthPattern) { if (pattern.growthRate > 5) { await this.investigatePotentialLeak(pattern); } } } analyzeMemoryGrowthPattern() { const patterns = []; if (this.memoryHistory.length >= 2) { const current = this.memoryHistory[this.memoryHistory.length - 1]; const previous = this.memoryHistory[this.memoryHistory.length - 2]; const heapGrowth = ((current.heapUsed - previous.heapUsed) / previous.heapUsed) * 100; if (heapGrowth > 0) { patterns.push({ type: 'heap', growthRate: heapGrowth, size: current.heapUsed, }); } const externalGrowth = ((current.external - previous.external) / previous.external) * 100; if (externalGrowth > 0) { patterns.push({ type: 'external', growthRate: externalGrowth, size: current.external, }); } } return patterns; } async investigatePotentialLeak(pattern) { const leakId = `leak_${pattern.type}_${Date.now()}`; const existingLeak = Array.from(this.memoryLeaks.values()).find((leak) => leak.type === pattern.type && leak.status === 'active'); if (existingLeak) { existingLeak.growth = pattern.growthRate; existingLeak.size = pattern.size; existingLeak.lastSeen = new Date(); if (pattern.growthRate > 20) { existingLeak.severity = 'critical'; } else if (pattern.growthRate > 10) { existingLeak.severity = 'high'; } this.leakSubject.next(existingLeak); } else { const leak = { id: leakId, type: pattern.type, location: 'unknown', size: pattern.size, growth: pattern.growthRate, firstDetected: new Date(), lastSeen: new Date(), severity: pattern.growthRate > 20 ? 'critical' : pattern.growthRate > 10 ? 'high' : 'medium', status: 'active', }; this.memoryLeaks.set(leakId, leak); this.leakSubject.next(leak); this.logger.warn(`Potential memory leak detected: ${pattern.type} growing at ${pattern.growthRate.toFixed(1)}% per interval`); } } async runOptimizationCycle() { this.logger.log('Starting memory optimization cycle'); try { if (this.shouldRunGc()) { await this.triggerGarbageCollection(); } if (this.config.optimization.compression) { await this.compressMemory(); } if (this.config.optimization.memoryPooling) { await this.cleanupMemoryPools(); } await this.resolveMemoryLeaks(); this.logger.log('Memory optimization cycle completed'); } catch (error) { this.logger.error(`Memory optimization cycle failed: ${error.message}`); } } shouldRunGc() { if (!this.config.optimization.autoGc) return false; const currentMetrics = this.getCurrentMemoryMetrics(); return currentMetrics.heapUsedPercentage > this.config.optimization.gcThreshold; } async triggerGarbageCollection() { try { this.logger.log('Triggering garbage collection'); const beforeMetrics = this.getCurrentMemoryMetrics(); const startTime = Date.now(); if (typeof global.gc === 'function') { global.gc(); } else { this.createMemoryPressure(); } const duration = Date.now() - startTime; const afterMetrics = this.getCurrentMemoryMetrics(); const freedMemory = beforeMetrics.heapUsed - afterMetrics.heapUsed; const improvement = beforeMetrics.heapUsed > 0 ? (freedMemory / beforeMetrics.heapUsed) * 100 : 0; const optimization = { type: 'gc', timestamp: new Date(), duration, freedMemory, improvement, success: true, }; this.optimizations.push(optimization); this.optimizationSubject.next(optimization); this.logger.log(`Garbage collection completed: freed ${this.formatBytes(freedMemory)} (${improvement.toFixed(1)}% improvement)`); } catch (error) { this.logger.error(`Garbage collection failed: ${error.message}`); const optimization = { type: 'gc', timestamp: new Date(), duration: 0, freedMemory: 0, improvement: 0, success: false, error: error.message, }; this.optimizations.push(optimization); this.optimizationSubject.next(optimization); } } createMemoryPressure() { const pressure = []; for (let i = 0; i < 1000; i++) { pressure.push(new Array(1000).fill('pressure')); } pressure.length = 0; } async compressMemory() { try { this.logger.log('Compressing memory'); const beforeMetrics = this.getCurrentMemoryMetrics(); const startTime = Date.now(); await this.performMemoryCompression(); const duration = Date.now() - startTime; const afterMetrics = this.getCurrentMemoryMetrics(); const freedMemory = beforeMetrics.heapUsed - afterMetrics.heapUsed; const improvement = beforeMetrics.heapUsed > 0 ? (freedMemory / beforeMetrics.heapUsed) * 100 : 0; const optimization = { type: 'compression', timestamp: new Date(), duration, freedMemory, improvement, success: true, }; this.optimizations.push(optimization); this.optimizationSubject.next(optimization); } catch (error) { this.logger.error(`Memory compression failed: ${error.message}`); const optimization = { type: 'compression', timestamp: new Date(), duration: 0, freedMemory: 0, improvement: 0, success: false, error: error.message, }; this.optimizations.push(optimization); this.optimizationSubject.next(optimization); } } async performMemoryCompression() { await new Promise((resolve) => setTimeout(resolve, 100)); } async cleanupMemoryPools() { try { this.logger.log('Cleaning up memory pools'); const beforeMetrics = this.getCurrentMemoryMetrics(); const startTime = Date.now(); await this.performPoolCleanup(); const duration = Date.now() - startTime; const afterMetrics = this.getCurrentMemoryMetrics(); const freedMemory = beforeMetrics.heapUsed - afterMetrics.heapUsed; const improvement = beforeMetrics.heapUsed > 0 ? (freedMemory / beforeMetrics.heapUsed) * 100 : 0; const optimization = { type: 'cleanup', timestamp: new Date(), duration, freedMemory, improvement, success: true, }; this.optimizations.push(optimization); this.optimizationSubject.next(optimization); } catch (error) { this.logger.error(`Memory pool cleanup failed: ${error.message}`); const optimization = { type: 'cleanup', timestamp: new Date(), duration: 0, freedMemory: 0, improvement: 0, success: false, error: error.message, }; this.optimizations.push(optimization); this.optimizationSubject.next(optimization); } } async performPoolCleanup() { await new Promise((resolve) => setTimeout(resolve, 50)); } async resolveMemoryLeaks() { const activeLeaks = Array.from(this.memoryLeaks.values()).filter((leak) => leak.status === 'active'); for (const leak of activeLeaks) { const recentMetrics = this.memoryHistory.slice(-5); const growthTrend = this.calculateLeakGrowthTrend(leak); if (growthTrend < 1) { leak.status = 'resolved'; this.logger.log(`Memory leak resolved: ${leak.type} (${leak.id})`); } } } calculateLeakGrowthTrend(leak) { if (this.memoryHistory.length < 10) return 0; const recent = this.memoryHistory.slice(-5); const older = this.memoryHistory.slice(-10, -5); let recentAvg, olderAvg; if (leak.type === 'heap') { recentAvg = recent.reduce((sum, m) => sum + m.heapUsed, 0) / recent.length; olderAvg = older.reduce((sum, m) => sum + m.heapUsed, 0) / older.length; } else if (leak.type === 'external') { recentAvg = recent.reduce((sum, m) => sum + m.external, 0) / recent.length; olderAvg = older.reduce((sum, m) => sum + m.external, 0) / older.length; } else { return 0; } if (olderAvg === 0) return 0; return ((recentAvg - olderAvg) / olderAvg) * 100; } formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } getMemoryMetrics() { return [...this.memoryHistory]; } getCurrentMetrics() { return this.getCurrentMemoryMetrics(); } getMemoryLeaks() { return Array.from(this.memoryLeaks.values()); } getOptimizations() { return [...this.optimizations]; } getMetricsUpdates() { return this.metricsSubject.asObservable(); } getHealthUpdates() { return this.healthSubject.asObservable(); } getLeakUpdates() { return this.leakSubject.asObservable(); } getOptimizationUpdates() { return this.optimizationSubject.asObservable(); } async forceGarbageCollection() { return new Promise((resolve) => { const beforeMetrics = this.getCurrentMemoryMetrics(); const startTime = Date.now(); if (typeof global.gc === 'function') { global.gc(); const duration = Date.now() - startTime; const afterMetrics = this.getCurrentMemoryMetrics(); const freedMemory = beforeMetrics.heapUsed - afterMetrics.heapUsed; const improvement = beforeMetrics.heapUsed > 0 ? (freedMemory / beforeMetrics.heapUsed) * 100 : 0; const optimization = { type: 'gc', timestamp: new Date(), duration, freedMemory, improvement, success: true, }; resolve(optimization); } else { resolve({ type: 'gc', timestamp: new Date(), duration: 0, freedMemory: 0, improvement: 0, success: false, error: 'Garbage collection not available', }); } }); } async resolveLeak(leakId) { const leak = this.memoryLeaks.get(leakId); if (!leak) return false; leak.status = 'resolved'; this.leakSubject.next(leak); this.logger.log(`Memory leak manually resolved: ${leakId}`); return true; } async shutdown() { if (this.monitoringInterval) { clearInterval(this.monitoringInterval); } if (this.optimizationInterval) { clearInterval(this.optimizationInterval); } await this.triggerGarbageCollection(); this.logger.log('Memory optimizer service shutdown'); } }; exports.MemoryOptimizerService = MemoryOptimizerService; exports.MemoryOptimizerService = MemoryOptimizerService = MemoryOptimizerService_1 = __decorate([ (0, common_1.Injectable)(), __param(0, (0, common_2.Inject)('TELESCOPE_CONFIG')), __metadata("design:paramtypes", [Object]) ], MemoryOptimizerService); //# sourceMappingURL=memory-optimizer.service.js.map