@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
JavaScript
"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