/** * MemoryManager - Gestion intelligente de la mémoire * Surveille et optimise l'utilisation mémoire de l'application */ export interface MemoryStats { usedJSHeapSize: number; totalJSHeapSize: number; jsHeapSizeLimit: number; timestamp: number; } export interface CacheEntry { data: T; timestamp: number; accessCount: number; lastAccessed: number; } export class MemoryManager { private static instance: MemoryManager; private caches: Map>> = new Map(); private maxCacheSize = 100; private maxCacheAge = 5 * 60 * 1000; // 5 minutes private cleanupInterval: number | null = null; private memoryThreshold = 100 * 1024 * 1024; // 100MB private isMonitoring = false; private constructor() { this.startCleanupInterval(); } public static getInstance(): MemoryManager { if (!MemoryManager.instance) { MemoryManager.instance = new MemoryManager(); } return MemoryManager.instance; } /** * Démarre le monitoring de la mémoire */ startMonitoring(): void { if (this.isMonitoring) return; this.isMonitoring = true; this.logMemoryStats(); // Vérifier la mémoire toutes les 2 minutes setInterval(() => { this.checkMemoryUsage(); }, 120000); } /** * Arrête le monitoring de la mémoire */ stopMonitoring(): void { this.isMonitoring = false; if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; } } /** * Met en cache une valeur avec gestion automatique */ setCache(cacheName: string, key: string, value: T): void { if (!this.caches.has(cacheName)) { this.caches.set(cacheName, new Map()); } const cache = this.caches.get(cacheName)!; const now = Date.now(); // Vérifier si le cache est plein if (cache.size >= this.maxCacheSize) { this.evictOldestEntry(cache); } cache.set(key, { data: value, timestamp: now, accessCount: 0, lastAccessed: now }); } /** * Récupère une valeur du cache */ getCache(cacheName: string, key: string): T | null { const cache = this.caches.get(cacheName); if (!cache) return null; const entry = cache.get(key); if (!entry) return null; // Vérifier l'âge de l'entrée if (Date.now() - entry.timestamp > this.maxCacheAge) { cache.delete(key); return null; } // Mettre à jour les statistiques d'accès entry.accessCount++; entry.lastAccessed = Date.now(); return entry.data; } /** * Supprime une entrée du cache */ deleteCache(cacheName: string, key: string): boolean { const cache = this.caches.get(cacheName); if (!cache) return false; return cache.delete(key); } /** * Vide un cache complet */ clearCache(cacheName: string): void { const cache = this.caches.get(cacheName); if (cache) { cache.clear(); } } /** * Vide tous les caches */ clearAllCaches(): void { this.caches.forEach(cache => cache.clear()); } /** * Récupère les statistiques d'un cache */ getCacheStats(cacheName: string): { size: number; entries: any[] } { const cache = this.caches.get(cacheName); if (!cache) return { size: 0, entries: [] }; const entries = Array.from(cache.entries()).map(([key, entry]) => ({ key, age: Date.now() - entry.timestamp, accessCount: entry.accessCount, lastAccessed: entry.lastAccessed })); return { size: cache.size, entries }; } /** * Récupère les statistiques de mémoire */ getMemoryStats(): MemoryStats | null { const memory = (performance as any).memory; if (!memory) return null; return { usedJSHeapSize: memory.usedJSHeapSize, totalJSHeapSize: memory.totalJSHeapSize, jsHeapSizeLimit: memory.jsHeapSizeLimit, timestamp: Date.now() }; } /** * Vérifie l'utilisation mémoire et déclenche le nettoyage si nécessaire */ private checkMemoryUsage(): void { const stats = this.getMemoryStats(); if (!stats) return; if (stats.usedJSHeapSize > this.memoryThreshold) { this.performMemoryCleanup(); } } /** * Effectue un nettoyage de mémoire */ private performMemoryCleanup(): void { console.log('🧹 Performing memory cleanup...'); // Nettoyer les caches expirés this.cleanupExpiredEntries(); // Supprimer les entrées les moins utilisées this.evictLeastUsedEntries(); // Forcer le garbage collection si disponible if (window.gc) { window.gc(); } } /** * Nettoie les entrées expirées */ private cleanupExpiredEntries(): void { const now = Date.now(); this.caches.forEach(cache => { const keysToDelete: string[] = []; cache.forEach((entry, key) => { if (now - entry.timestamp > this.maxCacheAge) { keysToDelete.push(key); } }); keysToDelete.forEach(key => cache.delete(key)); }); } /** * Supprime les entrées les moins utilisées */ private evictLeastUsedEntries(): void { this.caches.forEach(cache => { if (cache.size <= this.maxCacheSize) return; // Trier par nombre d'accès et dernière utilisation const entries = Array.from(cache.entries()).sort((a, b) => { const scoreA = a[1].accessCount + (Date.now() - a[1].lastAccessed) / 1000; const scoreB = b[1].accessCount + (Date.now() - b[1].lastAccessed) / 1000; return scoreA - scoreB; }); // Supprimer les 20% les moins utilisés const toDelete = Math.ceil(entries.length * 0.2); for (let i = 0; i < toDelete; i++) { cache.delete(entries[i][0]); } }); } /** * Supprime l'entrée la plus ancienne d'un cache */ private evictOldestEntry(cache: Map>): void { let oldestKey = ''; let oldestTime = Date.now(); cache.forEach((entry, key) => { if (entry.timestamp < oldestTime) { oldestTime = entry.timestamp; oldestKey = key; } }); if (oldestKey) { cache.delete(oldestKey); } } /** * Démarre l'intervalle de nettoyage automatique */ private startCleanupInterval(): void { this.cleanupInterval = setInterval(() => { this.cleanupExpiredEntries(); }, 60000) as any; // Nettoyage toutes les minutes } /** * Log les statistiques de mémoire */ private logMemoryStats(): void { const stats = this.getMemoryStats(); if (stats) { console.log('📊 Memory Stats:', { used: `${Math.round(stats.usedJSHeapSize / 1024 / 1024)}MB`, total: `${Math.round(stats.totalJSHeapSize / 1024 / 1024)}MB`, limit: `${Math.round(stats.jsHeapSizeLimit / 1024 / 1024)}MB`, usage: `${Math.round((stats.usedJSHeapSize / stats.jsHeapSizeLimit) * 100)}%` }); } } /** * Récupère un rapport complet de la mémoire */ getMemoryReport(): { memory: MemoryStats | null; caches: Record; recommendations: string[]; } { const memory = this.getMemoryStats(); const caches: Record = {}; this.caches.forEach((cache, name) => { // Use cache variable console.log('Cache:', cache); caches[name] = this.getCacheStats(name); }); const recommendations: string[] = []; if (memory) { const usagePercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100; if (usagePercent > 80) { recommendations.push('High memory usage detected. Consider clearing caches.'); } if (usagePercent > 90) { recommendations.push('Critical memory usage. Immediate cleanup recommended.'); } } const totalCacheEntries = Object.values(caches).reduce((sum, cache) => sum + cache.size, 0); if (totalCacheEntries > 500) { recommendations.push('Large number of cache entries. Consider reducing cache size.'); } return { memory, caches, recommendations }; } } // Instance singleton pour l'application export const memoryManager = MemoryManager.getInstance();