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
};