Skip to main content
Glama
memory-manager.tsโ€ข16.9 kB
/** * Memory Manager * Task 4.3: Performance Optimization - Memory Management Component * * Advanced memory management for the educational content generation system: * - Intelligent garbage collection optimization * - Memory leak detection and prevention * - Resource cleanup automation * - Memory usage monitoring and alerts * - Cache size optimization */ import { EventEmitter } from 'events'; export interface MemorySnapshot { timestamp: number; heap_used: number; heap_total: number; external: number; array_buffers: number; rss: number; } export interface MemoryAlert { type: 'warning' | 'critical'; message: string; current_usage: number; threshold: number; recommended_action: string; } export interface MemoryManagerConfig { warning_threshold: number; // MB critical_threshold: number; // MB cleanup_interval: number; // milliseconds gc_interval: number; // milliseconds max_cache_size: number; // entries enable_leak_detection: boolean; auto_cleanup: boolean; } export class MemoryManager extends EventEmitter { private config: MemoryManagerConfig; private snapshots: MemorySnapshot[] = []; private cleanupInterval: NodeJS.Timeout | null = null; private gcInterval: NodeJS.Timeout | null = null; private leakDetectionInterval: NodeJS.Timeout | null = null; private resourceRegistry = new Map<string, WeakRef<any>>(); private cacheRegistry = new Map<string, { size: number; lastAccess: number }>(); constructor(config: MemoryManagerConfig) { super(); this.config = config; this.initializeMemoryManagement(); } private initializeMemoryManagement(): void { console.log('๐Ÿง  Initializing Memory Manager...'); // Take initial snapshot this.takeMemorySnapshot(); // Setup periodic cleanup if (this.config.auto_cleanup) { this.setupPeriodicCleanup(); } // Setup garbage collection optimization this.setupGarbageCollectionOptimization(); // Setup leak detection if (this.config.enable_leak_detection) { this.setupLeakDetection(); } console.log('โœ… Memory management initialized'); this.logMemoryStatus(); } /** * Takes a memory snapshot and analyzes usage patterns */ public takeMemorySnapshot(): MemorySnapshot { const memUsage = process.memoryUsage(); const snapshot: MemorySnapshot = { timestamp: Date.now(), heap_used: Math.round(memUsage.heapUsed / 1024 / 1024 * 100) / 100, heap_total: Math.round(memUsage.heapTotal / 1024 / 1024 * 100) / 100, external: Math.round(memUsage.external / 1024 / 1024 * 100) / 100, array_buffers: Math.round(memUsage.arrayBuffers / 1024 / 1024 * 100) / 100, rss: Math.round(memUsage.rss / 1024 / 1024 * 100) / 100 }; this.snapshots.push(snapshot); // Keep only last 100 snapshots if (this.snapshots.length > 100) { this.snapshots = this.snapshots.slice(-100); } // Check for alerts this.checkMemoryAlerts(snapshot); this.emit('snapshot-taken', snapshot); return snapshot; } /** * Performs intelligent memory cleanup */ public async performCleanup(force: boolean = false): Promise<{ before: MemorySnapshot; after: MemorySnapshot; freed: number; actions: string[]; }> { const before = this.takeMemorySnapshot(); const actions: string[] = []; console.log('๐Ÿงน Performing memory cleanup...'); // 1. Clean expired cache entries const cacheFreed = this.cleanExpiredCacheEntries(); if (cacheFreed > 0) { actions.push(`Cleared ${cacheFreed} expired cache entries`); } // 2. Clean orphaned resources const resourcesFreed = this.cleanOrphanedResources(); if (resourcesFreed > 0) { actions.push(`Cleaned ${resourcesFreed} orphaned resources`); } // 3. Force garbage collection if needed if (force || before.heap_used > this.config.warning_threshold) { this.forceGarbageCollection(); actions.push('Forced garbage collection'); } // 4. Optimize V8 heap if critically high if (before.heap_used > this.config.critical_threshold) { await this.optimizeV8Heap(); actions.push('Optimized V8 heap'); } const after = this.takeMemorySnapshot(); const freed = before.heap_used - after.heap_used; console.log(`โœ… Memory cleanup completed: freed ${freed.toFixed(2)}MB`); this.emit('cleanup-completed', { before, after, freed, actions }); return { before, after, freed, actions }; } /** * Registers a resource for automatic cleanup tracking */ public registerResource(id: string, resource: any, metadata?: any): void { this.resourceRegistry.set(id, new WeakRef(resource)); // Track in cache registry if it's a cache item if (metadata?.type === 'cache') { this.cacheRegistry.set(id, { size: metadata.size || 1, lastAccess: Date.now() }); } } /** * Unregisters a resource */ public unregisterResource(id: string): void { this.resourceRegistry.delete(id); this.cacheRegistry.delete(id); } /** * Gets current memory usage statistics */ public getMemoryStats(): { current: MemorySnapshot; trend: 'increasing' | 'decreasing' | 'stable'; health: 'good' | 'warning' | 'critical'; recommendations: string[]; } { const current = this.takeMemorySnapshot(); const trend = this.calculateMemoryTrend(); const health = this.assessMemoryHealth(current); const recommendations = this.generateRecommendations(current, trend, health); return { current, trend, health, recommendations }; } /** * Optimizes memory usage for specific operations */ public async optimizeForOperation( operationType: 'content-generation' | 'widget-mapping' | 'browser-automation', operation: Function ): Promise<any> { // Pre-operation optimization const beforeSnapshot = this.takeMemorySnapshot(); if (beforeSnapshot.heap_used > this.config.warning_threshold * 0.8) { await this.performCleanup(); } // Operation-specific optimizations this.applyOperationOptimizations(operationType); try { // Execute operation const result = await operation(); // Post-operation cleanup const afterSnapshot = this.takeMemorySnapshot(); const memoryDelta = afterSnapshot.heap_used - beforeSnapshot.heap_used; if (memoryDelta > 50) { // If operation used more than 50MB console.log(`โš ๏ธ High memory usage operation: +${memoryDelta.toFixed(2)}MB`); setTimeout(() => this.performCleanup(), 1000); } this.emit('operation-completed', { type: operationType, memory_delta: memoryDelta, before: beforeSnapshot, after: afterSnapshot }); return result; } catch (error) { // Cleanup on error this.performCleanup(true); throw error; } } /** * Detects and reports memory leaks */ public detectMemoryLeaks(): { leaks_detected: boolean; suspicious_growth: boolean; recommendations: string[]; details: any; } { const recentSnapshots = this.snapshots.slice(-10); if (recentSnapshots.length < 5) { return { leaks_detected: false, suspicious_growth: false, recommendations: ['Insufficient data for leak detection'], details: null }; } // Check for consistent memory growth const growth = this.calculateConsistentGrowth(recentSnapshots); const suspiciousGrowth = growth > 5; // More than 5MB consistent growth // Check for orphaned resources const orphanedResources = this.findOrphanedResources(); const leaksDetected = orphanedResources.length > 0; const recommendations: string[] = []; if (suspiciousGrowth) { recommendations.push('Monitor for memory leaks - consistent growth detected'); recommendations.push('Consider reducing cache sizes'); } if (leaksDetected) { recommendations.push(`Clean up ${orphanedResources.length} orphaned resources`); recommendations.push('Review resource cleanup patterns'); } if (!suspiciousGrowth && !leaksDetected) { recommendations.push('Memory usage patterns are healthy'); } return { leaks_detected: leaksDetected, suspicious_growth: suspiciousGrowth, recommendations, details: { growth_rate: growth, orphaned_resources: orphanedResources.length, recent_snapshots: recentSnapshots.length } }; } // Private methods private setupPeriodicCleanup(): void { this.cleanupInterval = setInterval(() => { this.performCleanup(); }, this.config.cleanup_interval); } private setupGarbageCollectionOptimization(): void { this.gcInterval = setInterval(() => { const currentUsage = this.getCurrentMemoryUsage(); if (currentUsage > this.config.warning_threshold * 0.7) { this.forceGarbageCollection(); } }, this.config.gc_interval); } private setupLeakDetection(): void { this.leakDetectionInterval = setInterval(() => { const leakReport = this.detectMemoryLeaks(); if (leakReport.leaks_detected || leakReport.suspicious_growth) { this.emit('memory-leak-detected', leakReport); console.log('โš ๏ธ Potential memory leak detected:', leakReport.recommendations); } }, 300000); // Check every 5 minutes } private checkMemoryAlerts(snapshot: MemorySnapshot): void { if (snapshot.heap_used > this.config.critical_threshold) { const alert: MemoryAlert = { type: 'critical', message: 'Critical memory usage threshold exceeded', current_usage: snapshot.heap_used, threshold: this.config.critical_threshold, recommended_action: 'Immediate cleanup and garbage collection required' }; this.emit('memory-alert', alert); console.log('๐Ÿšจ CRITICAL MEMORY ALERT:', alert.message); // Auto-cleanup for critical situations setTimeout(() => this.performCleanup(true), 100); } else if (snapshot.heap_used > this.config.warning_threshold) { const alert: MemoryAlert = { type: 'warning', message: 'Memory usage warning threshold exceeded', current_usage: snapshot.heap_used, threshold: this.config.warning_threshold, recommended_action: 'Consider cleanup and optimization' }; this.emit('memory-alert', alert); console.log('โš ๏ธ Memory warning:', alert.message); } } private cleanExpiredCacheEntries(): number { const now = Date.now(); const expiredEntries: string[] = []; const maxAge = 30 * 60 * 1000; // 30 minutes for (const [id, cacheInfo] of this.cacheRegistry.entries()) { if (now - cacheInfo.lastAccess > maxAge) { expiredEntries.push(id); } } expiredEntries.forEach(id => { this.cacheRegistry.delete(id); this.resourceRegistry.delete(id); }); return expiredEntries.length; } private cleanOrphanedResources(): number { const orphaned: string[] = []; for (const [id, weakRef] of this.resourceRegistry.entries()) { if (weakRef.deref() === undefined) { orphaned.push(id); } } orphaned.forEach(id => { this.resourceRegistry.delete(id); this.cacheRegistry.delete(id); }); return orphaned.length; } private findOrphanedResources(): string[] { const orphaned: string[] = []; for (const [id, weakRef] of this.resourceRegistry.entries()) { if (weakRef.deref() === undefined) { orphaned.push(id); } } return orphaned; } private forceGarbageCollection(): void { if (global.gc) { global.gc(); console.log('๐Ÿ—‘๏ธ Forced garbage collection'); } else { console.log('โš ๏ธ Garbage collection not available (run with --expose-gc)'); } } private async optimizeV8Heap(): Promise<void> { // Force full GC multiple times if (global.gc) { for (let i = 0; i < 3; i++) { global.gc(); await new Promise(resolve => setTimeout(resolve, 100)); } } // Clear all non-essential caches this.cacheRegistry.clear(); console.log('๐ŸŽฏ V8 heap optimization completed'); } private applyOperationOptimizations(operationType: string): void { switch (operationType) { case 'content-generation': // Pre-clean cache if it's getting large if (this.cacheRegistry.size > this.config.max_cache_size * 0.8) { this.cleanExpiredCacheEntries(); } break; case 'widget-mapping': // Ensure we have enough memory for widget processing if (this.getCurrentMemoryUsage() > this.config.warning_threshold * 0.9) { this.forceGarbageCollection(); } break; case 'browser-automation': // Browser operations are memory-intensive, clean up beforehand this.performCleanup(); break; } } private calculateMemoryTrend(): 'increasing' | 'decreasing' | 'stable' { if (this.snapshots.length < 3) return 'stable'; const recent = this.snapshots.slice(-5); const older = this.snapshots.slice(-10, -5); if (older.length === 0) return 'stable'; const recentAvg = recent.reduce((sum, s) => sum + s.heap_used, 0) / recent.length; const olderAvg = older.reduce((sum, s) => sum + s.heap_used, 0) / older.length; const changePercent = ((recentAvg - olderAvg) / olderAvg) * 100; if (changePercent > 10) return 'increasing'; if (changePercent < -10) return 'decreasing'; return 'stable'; } private assessMemoryHealth(snapshot: MemorySnapshot): 'good' | 'warning' | 'critical' { if (snapshot.heap_used > this.config.critical_threshold) return 'critical'; if (snapshot.heap_used > this.config.warning_threshold) return 'warning'; return 'good'; } private generateRecommendations( snapshot: MemorySnapshot, trend: string, health: string ): string[] { const recommendations: string[] = []; if (health === 'critical') { recommendations.push('Immediate cleanup required'); recommendations.push('Consider restarting the process if memory cannot be freed'); } else if (health === 'warning') { recommendations.push('Schedule cleanup operations'); recommendations.push('Monitor memory usage closely'); } if (trend === 'increasing') { recommendations.push('Memory usage is trending upward - investigate potential leaks'); recommendations.push('Consider reducing cache sizes'); } if (this.cacheRegistry.size > this.config.max_cache_size) { recommendations.push('Cache size is too large - clean expired entries'); } if (recommendations.length === 0) { recommendations.push('Memory usage is healthy'); } return recommendations; } private calculateConsistentGrowth(snapshots: MemorySnapshot[]): number { if (snapshots.length < 2) return 0; const first = snapshots[0].heap_used; const last = snapshots[snapshots.length - 1].heap_used; return last - first; } private getCurrentMemoryUsage(): number { return Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100; } private logMemoryStatus(): void { const current = this.getCurrentMemoryUsage(); console.log(`๐Ÿ’พ Memory Status: ${current}MB used (Warning: ${this.config.warning_threshold}MB, Critical: ${this.config.critical_threshold}MB)`); } /** * Public methods for configuration and control */ public updateConfig(newConfig: Partial<MemoryManagerConfig>): void { this.config = { ...this.config, ...newConfig }; console.log('๐Ÿ”ง Memory manager config updated'); } public getSnapshots(): MemorySnapshot[] { return [...this.snapshots]; } public clearSnapshots(): void { this.snapshots = []; console.log('๐Ÿงน Memory snapshots cleared'); } public destroy(): void { if (this.cleanupInterval) clearInterval(this.cleanupInterval); if (this.gcInterval) clearInterval(this.gcInterval); if (this.leakDetectionInterval) clearInterval(this.leakDetectionInterval); this.resourceRegistry.clear(); this.cacheRegistry.clear(); this.snapshots = []; console.log('๐Ÿ”š Memory manager destroyed'); } } // Default configuration export const DEFAULT_MEMORY_CONFIG: MemoryManagerConfig = { warning_threshold: 512, // 512MB critical_threshold: 1024, // 1GB cleanup_interval: 300000, // 5 minutes gc_interval: 120000, // 2 minutes max_cache_size: 1000, // entries enable_leak_detection: true, auto_cleanup: true };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkm097git/euconquisto-composer-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server