UNPKG

@ahmedhegazee/nestjs-telescope

Version:

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

318 lines 12.3 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 EnhancedMemoryManagerService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnhancedMemoryManagerService = void 0; const common_1 = require("@nestjs/common"); let EnhancedMemoryManagerService = EnhancedMemoryManagerService_1 = class EnhancedMemoryManagerService { constructor(config = {}) { this.logger = new common_1.Logger(EnhancedMemoryManagerService_1.name); this.timelines = new Map(); this.metrics = new Map(); this.cleanupTimer = null; this.lastCleanupTime = 0; this.config = { enabled: true, maxTimelineSize: 1000, metricsRetentionMs: 300000, cleanupIntervalMs: 60000, memoryThresholdMB: 100, autoCleanup: true, compressionEnabled: true, ...config }; if (this.config.enabled && this.config.autoCleanup) { this.startCleanupTimer(); } this.logger.debug('Enhanced Memory Manager initialized', this.config); } addTimelineEntry(timelineId, data) { if (!this.config.enabled) { return; } try { let timeline = this.timelines.get(timelineId); if (!timeline) { timeline = []; this.timelines.set(timelineId, timeline); } const entry = { timestamp: Date.now(), data: this.config.compressionEnabled ? this.compressData(data) : data, compressed: this.config.compressionEnabled }; timeline.push(entry); if (timeline.length > this.config.maxTimelineSize) { const removeCount = timeline.length - this.config.maxTimelineSize; timeline.splice(0, removeCount); } this.checkMemoryUsage(); } catch (error) { this.logger.error(`Failed to add timeline entry for ${timelineId}:`, error); } } addMetricsEntry(key, value, ttlMs) { if (!this.config.enabled) { return; } try { let metricsArray = this.metrics.get(key); if (!metricsArray) { metricsArray = []; this.metrics.set(key, metricsArray); } const entry = { timestamp: Date.now(), key, value: this.config.compressionEnabled ? this.compressData(value) : value, ttl: ttlMs ? Date.now() + ttlMs : undefined }; metricsArray.push(entry); if (ttlMs) { this.cleanupExpiredMetrics(key); } this.checkMemoryUsage(); } catch (error) { this.logger.error(`Failed to add metrics entry for ${key}:`, error); } } getTimelineEntries(timelineId, fromTimestamp, toTimestamp) { try { const timeline = this.timelines.get(timelineId); if (!timeline) { return []; } let entries = timeline; if (fromTimestamp || toTimestamp) { entries = timeline.filter(entry => { if (fromTimestamp && entry.timestamp < fromTimestamp) return false; if (toTimestamp && entry.timestamp > toTimestamp) return false; return true; }); } return entries.map(entry => ({ timestamp: entry.timestamp, data: entry.compressed ? this.decompressData(entry.data) : entry.data })); } catch (error) { this.logger.error(`Failed to get timeline entries for ${timelineId}:`, error); return []; } } getLatestMetrics(key, limit = 10) { try { const metricsArray = this.metrics.get(key); if (!metricsArray) { return []; } this.cleanupExpiredMetrics(key); const validEntries = metricsArray.slice(-limit); return validEntries.map(entry => ({ timestamp: entry.timestamp, key: entry.key, value: this.config.compressionEnabled ? this.decompressData(entry.value) : entry.value })); } catch (error) { this.logger.error(`Failed to get latest metrics for ${key}:`, error); return []; } } getMemoryStats() { const timelineEntries = Array.from(this.timelines.values()).reduce((sum, timeline) => sum + timeline.length, 0); const metricsEntries = Array.from(this.metrics.values()).reduce((sum, metrics) => sum + metrics.length, 0); const memoryUsage = process.memoryUsage(); const memoryUsageMB = memoryUsage.heapUsed / 1024 / 1024; return { totalAllocated: timelineEntries + metricsEntries, timelineEntries, metricsEntries, lastCleanup: this.lastCleanupTime || 0, memoryUsageMB, compressionRatio: this.calculateCompressionRatio() }; } async cleanup() { if (!this.config.enabled) { return; } try { const startTime = Date.now(); let cleanedEntries = 0; cleanedEntries += this.cleanupOldTimelineEntries(); cleanedEntries += this.cleanupOldMetricsEntries(); cleanedEntries += this.cleanupAllExpiredMetrics(); if (global.gc) { global.gc(); } const duration = Date.now() - startTime; this.lastCleanupTime = Date.now(); this.logger.debug(`Memory cleanup completed: ${cleanedEntries} entries removed in ${duration}ms`); } catch (error) { this.logger.error('Memory cleanup failed:', error); } } clearTimeline(timelineId) { try { this.timelines.delete(timelineId); this.logger.debug(`Timeline cleared: ${timelineId}`); } catch (error) { this.logger.error(`Failed to clear timeline ${timelineId}:`, error); } } clearMetrics(key) { try { this.metrics.delete(key); this.logger.debug(`Metrics cleared: ${key}`); } catch (error) { this.logger.error(`Failed to clear metrics ${key}:`, error); } } clearAll() { try { this.timelines.clear(); this.metrics.clear(); this.logger.debug('All memory data cleared'); } catch (error) { this.logger.error('Failed to clear all data:', error); } } shutdown() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = null; } this.logger.debug('Memory manager shutdown'); } startCleanupTimer() { this.cleanupTimer = setInterval(() => { this.cleanup(); }, this.config.cleanupIntervalMs); } cleanupOldTimelineEntries() { let removedCount = 0; const cutoff = Date.now() - this.config.metricsRetentionMs; for (const [timelineId, timeline] of this.timelines.entries()) { const originalLength = timeline.length; const newTimeline = timeline.filter(entry => entry.timestamp >= cutoff); if (newTimeline.length > this.config.maxTimelineSize) { newTimeline.splice(0, newTimeline.length - this.config.maxTimelineSize); } this.timelines.set(timelineId, newTimeline); removedCount += originalLength - newTimeline.length; } return removedCount; } cleanupOldMetricsEntries() { let removedCount = 0; const cutoff = Date.now() - this.config.metricsRetentionMs; for (const [key, metricsArray] of this.metrics.entries()) { const originalLength = metricsArray.length; const newMetrics = metricsArray.filter(entry => entry.timestamp >= cutoff); this.metrics.set(key, newMetrics); removedCount += originalLength - newMetrics.length; } return removedCount; } cleanupAllExpiredMetrics() { let removedCount = 0; for (const key of this.metrics.keys()) { removedCount += this.cleanupExpiredMetrics(key); } return removedCount; } cleanupExpiredMetrics(key) { const metricsArray = this.metrics.get(key); if (!metricsArray) { return 0; } const now = Date.now(); const originalLength = metricsArray.length; const validMetrics = metricsArray.filter(entry => !entry.ttl || entry.ttl > now); this.metrics.set(key, validMetrics); return originalLength - validMetrics.length; } checkMemoryUsage() { const memoryUsage = process.memoryUsage(); const memoryUsageMB = memoryUsage.heapUsed / 1024 / 1024; if (memoryUsageMB > this.config.memoryThresholdMB) { this.logger.warn(`Memory threshold exceeded: ${memoryUsageMB.toFixed(2)}MB > ${this.config.memoryThresholdMB}MB`); if (this.config.autoCleanup) { setImmediate(() => this.cleanup()); } } } compressData(data) { try { if (typeof data === 'object' && data !== null) { const jsonString = JSON.stringify(data); if (jsonString.length > 1000) { return this.simplifyObject(data); } } return data; } catch (error) { return data; } } decompressData(data) { return data; } simplifyObject(obj) { if (Array.isArray(obj)) { return obj.length > 100 ? obj.slice(0, 100) : obj; } if (typeof obj === 'object' && obj !== null) { const simplified = {}; let propertyCount = 0; for (const [key, value] of Object.entries(obj)) { if (propertyCount >= 50) break; if (typeof value === 'string' && value.length > 500) { simplified[key] = value.substring(0, 500) + '...'; } else if (typeof value === 'object') { simplified[key] = this.simplifyObject(value); } else { simplified[key] = value; } propertyCount++; } return simplified; } return obj; } calculateCompressionRatio() { const timelineCount = Array.from(this.timelines.values()).reduce((sum, timeline) => sum + timeline.length, 0); const metricsCount = Array.from(this.metrics.values()).reduce((sum, metrics) => sum + metrics.length, 0); if (timelineCount + metricsCount === 0) { return 1; } return 0.7; } }; exports.EnhancedMemoryManagerService = EnhancedMemoryManagerService; exports.EnhancedMemoryManagerService = EnhancedMemoryManagerService = EnhancedMemoryManagerService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [Object]) ], EnhancedMemoryManagerService); //# sourceMappingURL=enhanced-memory-manager.service.js.map