ihm_client/src/services/memory-manager.ts
NicolasCantu 422ceef3e9 ci: docker_tag=dev-test
**Motivations :**
- Corriger la détection des tokens du faucet en forçant la synchronisation du wallet
- Ajouter des messages utilisateur compréhensibles pour remplacer les logs techniques
- S'assurer que le scan des blocs est effectué après création/restauration du wallet

**Modifications :**
- Ajout de la méthode updateUserStatus() pour afficher des messages clairs à l'utilisateur
- Messages utilisateur dans waitForAmount() : synchronisation, demande de tokens, confirmation
- Messages utilisateur dans parseNewTx() : transaction reçue, wallet mis à jour
- Synchronisation forcée du wallet après création/restauration dans router.ts
- Messages de statut dans updateDeviceBlockHeight() pour informer l'utilisateur
- Logs de debugging étendus pour diagnostiquer les problèmes de faucet

**Pages affectées :**
- src/services/service.ts (méthodes updateUserStatus, waitForAmount, parseNewTx, updateDeviceBlockHeight)
- src/router.ts (synchronisation après création/restauration du wallet)
2025-10-24 00:36:41 +02:00

335 lines
8.2 KiB
TypeScript

/**
* 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<T> {
data: T;
timestamp: number;
accessCount: number;
lastAccessed: number;
}
export class MemoryManager {
private static instance: MemoryManager;
private caches: Map<string, Map<string, CacheEntry<any>>> = new Map();
private maxCacheSize = 0; // Disabled caches completely
private maxCacheAge = 0; // No cache expiry
private cleanupInterval: number | null = null;
private memoryThreshold = 200 * 1024 * 1024; // 200MB (increased from 100MB)
private isMonitoring = false;
private constructor() {
// Disabled to save memory
// 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<T>(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<T>(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<string, CacheEntry<any>>): 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();
}, 120000) as any; // Nettoyage toutes les 2 minutes (reduced frequency)
}
/**
* 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<string, { size: number; entries: any[] }>;
recommendations: string[];
} {
const memory = this.getMemoryStats();
const caches: Record<string, { size: number; entries: any[] }> = {};
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();