/** * Performance Optimizer * Advanced performance monitoring and optimization for GreenPlus by GXS */ export class PerformanceOptimizer { constructor() { this.metrics = new Map(); this.optimizations = new Map(); this.cache = new Map(); this.observers = []; // Performance thresholds this.thresholds = { analysisTime: 5000, // 5 seconds max imageProcessing: 3000, // 3 seconds max audioProcessing: 4000, // 4 seconds max databaseQuery: 1000, // 1 second max memoryUsage: 100 * 1024 * 1024, // 100MB max cacheSize: 50 * 1024 * 1024 // 50MB max }; this.initializeOptimizations(); } /** * Initialize performance optimizations */ initializeOptimizations() { // Enable performance monitoring this.enablePerformanceMonitoring(); // Setup caching strategies this.setupCaching(); // Initialize lazy loading this.setupLazyLoading(); // Setup memory management this.setupMemoryManagement(); console.log('⚡ Performance optimizer initialized'); } /** * Enable comprehensive performance monitoring */ enablePerformanceMonitoring() { // Monitor navigation timing if ('performance' in window && 'getEntriesByType' in performance) { const navigationEntries = performance.getEntriesByType('navigation'); if (navigationEntries.length > 0) { const nav = navigationEntries[0]; this.recordMetric('pageLoad', nav.loadEventEnd - nav.fetchStart); } } // Monitor resource loading if ('PerformanceObserver' in window) { const resourceObserver = new PerformanceObserver((list) => { list.getEntries().forEach(entry => { if (entry.name.includes('chunk') || entry.name.includes('.js')) { this.recordMetric('resourceLoad', entry.duration, entry.name); } }); }); resourceObserver.observe({ entryTypes: ['resource'] }); this.observers.push(resourceObserver); } // Monitor memory usage this.startMemoryMonitoring(); } /** * Setup intelligent caching system */ setupCaching() { // Analysis result cache with TTL this.analysisCache = new Map(); this.cacheTimestamps = new Map(); this.cacheTTL = 5 * 60 * 1000; // 5 minutes // Image processing cache this.imageCache = new Map(); this.imageCacheSize = 0; // Audio processing cache this.audioCache = new Map(); this.audioCacheSize = 0; // Database query cache this.dbCache = new Map(); this.dbCacheTimestamps = new Map(); this.dbCacheTTL = 2 * 60 * 1000; // 2 minutes } /** * Setup lazy loading for components and resources */ setupLazyLoading() { // Intersection Observer for lazy loading if ('IntersectionObserver' in window) { this.lazyLoadObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.loadLazyContent(entry.target); } }); }, { rootMargin: '50px' }); } } /** * Setup memory management */ setupMemoryManagement() { // Periodic cleanup setInterval(() => { this.cleanupCache(); this.cleanupMemory(); }, 60000); // Every minute // Cleanup on page visibility change document.addEventListener('visibilitychange', () => { if (document.hidden) { this.cleanupMemory(); } }); } /** * Monitor memory usage */ startMemoryMonitoring() { if ('memory' in performance) { setInterval(() => { const memory = performance.memory; this.recordMetric('memoryUsed', memory.usedJSHeapSize); this.recordMetric('memoryTotal', memory.totalJSHeapSize); // Trigger cleanup if memory usage is high if (memory.usedJSHeapSize > this.thresholds.memoryUsage) { this.cleanupMemory(); } }, 30000); // Every 30 seconds } } /** * Optimize image analysis performance */ async optimizeImageAnalysis(imageSource, analysisFunction) { const startTime = performance.now(); try { // Check cache first const cacheKey = this.generateImageCacheKey(imageSource); const cached = this.getFromCache('image', cacheKey); if (cached) { this.recordMetric('imageCacheHit', performance.now() - startTime); return cached; } // Optimize image before analysis const optimizedImage = await this.optimizeImage(imageSource); // Run analysis with timeout const result = await this.withTimeout( analysisFunction(optimizedImage), this.thresholds.imageProcessing ); // Cache result this.setCache('image', cacheKey, result); const duration = performance.now() - startTime; this.recordMetric('imageAnalysis', duration); return result; } catch (error) { const duration = performance.now() - startTime; this.recordMetric('imageAnalysisError', duration); throw error; } } /** * Optimize audio analysis performance */ async optimizeAudioAnalysis(audioData, analysisFunction) { const startTime = performance.now(); try { // Check cache first const cacheKey = this.generateAudioCacheKey(audioData); const cached = this.getFromCache('audio', cacheKey); if (cached) { this.recordMetric('audioCacheHit', performance.now() - startTime); return cached; } // Optimize audio before analysis const optimizedAudio = await this.optimizeAudio(audioData); // Run analysis with timeout const result = await this.withTimeout( analysisFunction(optimizedAudio), this.thresholds.audioProcessing ); // Cache result this.setCache('audio', cacheKey, result); const duration = performance.now() - startTime; this.recordMetric('audioAnalysis', duration); return result; } catch (error) { const duration = performance.now() - startTime; this.recordMetric('audioAnalysisError', duration); throw error; } } /** * Optimize database operations */ async optimizeDbOperation(operation, cacheKey = null) { const startTime = performance.now(); try { // Check cache if key provided if (cacheKey) { const cached = this.getFromCache('db', cacheKey); if (cached) { this.recordMetric('dbCacheHit', performance.now() - startTime); return cached; } } // Run operation with timeout const result = await this.withTimeout( operation(), this.thresholds.databaseQuery ); // Cache result if key provided if (cacheKey) { this.setCache('db', cacheKey, result); } const duration = performance.now() - startTime; this.recordMetric('dbOperation', duration); return result; } catch (error) { const duration = performance.now() - startTime; this.recordMetric('dbOperationError', duration); throw error; } } /** * Optimize image for analysis */ async optimizeImage(imageSource) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // Optimize dimensions (max 1920x1080) const maxWidth = 1920; const maxHeight = 1080; let { width, height } = img; if (width > maxWidth || height > maxHeight) { const ratio = Math.min(maxWidth / width, maxHeight / height); width *= ratio; height *= ratio; } canvas.width = width; canvas.height = height; // Draw with high quality ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; ctx.drawImage(img, 0, 0, width, height); // Convert to optimized format const optimizedDataUrl = canvas.toDataURL('image/jpeg', 0.9); resolve(optimizedDataUrl); } catch (error) { reject(error); } }; img.onerror = reject; img.src = typeof imageSource === 'string' ? imageSource : URL.createObjectURL(imageSource); }); } /** * Optimize audio for analysis */ async optimizeAudio(audioData) { // For now, return as-is. In a real implementation, this could: // - Compress audio // - Normalize volume // - Remove silence // - Convert to optimal format return audioData; } /** * Add timeout to promises */ withTimeout(promise, timeoutMs) { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs) ) ]); } /** * Cache management */ setCache(type, key, value) { const now = Date.now(); switch (type) { case 'image': const imageSize = this.estimateSize(value); if (this.imageCacheSize + imageSize > this.thresholds.cacheSize) { this.cleanupImageCache(); } this.imageCache.set(key, value); this.imageCacheSize += imageSize; break; case 'audio': const audioSize = this.estimateSize(value); if (this.audioCacheSize + audioSize > this.thresholds.cacheSize) { this.cleanupAudioCache(); } this.audioCache.set(key, value); this.audioCacheSize += audioSize; break; case 'db': this.dbCache.set(key, value); this.dbCacheTimestamps.set(key, now); break; default: this.cache.set(key, value); this.cacheTimestamps.set(key, now); } } getFromCache(type, key) { const now = Date.now(); switch (type) { case 'image': return this.imageCache.get(key); case 'audio': return this.audioCache.get(key); case 'db': const dbTimestamp = this.dbCacheTimestamps.get(key); if (dbTimestamp && (now - dbTimestamp) < this.dbCacheTTL) { return this.dbCache.get(key); } this.dbCache.delete(key); this.dbCacheTimestamps.delete(key); return null; default: const timestamp = this.cacheTimestamps.get(key); if (timestamp && (now - timestamp) < this.cacheTTL) { return this.cache.get(key); } this.cache.delete(key); this.cacheTimestamps.delete(key); return null; } } /** * Cache cleanup */ cleanupCache() { const now = Date.now(); // Cleanup general cache for (const [key, timestamp] of this.cacheTimestamps.entries()) { if (now - timestamp > this.cacheTTL) { this.cache.delete(key); this.cacheTimestamps.delete(key); } } // Cleanup DB cache for (const [key, timestamp] of this.dbCacheTimestamps.entries()) { if (now - timestamp > this.dbCacheTTL) { this.dbCache.delete(key); this.dbCacheTimestamps.delete(key); } } } cleanupImageCache() { // Remove oldest entries if cache is too large const entries = Array.from(this.imageCache.entries()); const toRemove = Math.ceil(entries.length * 0.3); // Remove 30% for (let i = 0; i < toRemove; i++) { const [key] = entries[i]; this.imageCache.delete(key); } this.imageCacheSize = this.imageCacheSize * 0.7; // Approximate } cleanupAudioCache() { // Remove oldest entries if cache is too large const entries = Array.from(this.audioCache.entries()); const toRemove = Math.ceil(entries.length * 0.3); // Remove 30% for (let i = 0; i < toRemove; i++) { const [key] = entries[i]; this.audioCache.delete(key); } this.audioCacheSize = this.audioCacheSize * 0.7; // Approximate } cleanupMemory() { // Force garbage collection if available if (window.gc) { window.gc(); } // Clear large caches if memory pressure is high if ('memory' in performance) { const memory = performance.memory; if (memory.usedJSHeapSize > this.thresholds.memoryUsage * 0.8) { this.imageCache.clear(); this.audioCache.clear(); this.imageCacheSize = 0; this.audioCacheSize = 0; } } } /** * Generate cache keys */ generateImageCacheKey(imageSource) { if (typeof imageSource === 'string') { return `img_${this.hashString(imageSource)}`; } else if (imageSource instanceof Blob) { return `img_${imageSource.size}_${imageSource.type}_${imageSource.lastModified || Date.now()}`; } return `img_${Date.now()}_${Math.random()}`; } generateAudioCacheKey(audioData) { if (audioData instanceof Blob) { return `audio_${audioData.size}_${audioData.type}_${audioData.lastModified || Date.now()}`; } return `audio_${Date.now()}_${Math.random()}`; } hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash).toString(36); } /** * Estimate object size in bytes */ estimateSize(obj) { const jsonString = JSON.stringify(obj); return new Blob([jsonString]).size; } /** * Record performance metrics */ recordMetric(name, value, details = null) { const metric = { name, value, details, timestamp: Date.now() }; if (!this.metrics.has(name)) { this.metrics.set(name, []); } const metrics = this.metrics.get(name); metrics.push(metric); // Keep only last 100 metrics per type if (metrics.length > 100) { metrics.shift(); } // Log performance issues if (this.isPerformanceIssue(name, value)) { console.warn(`⚠️ Performance issue: ${name} took ${value}ms`); } } isPerformanceIssue(name, value) { const thresholds = { imageAnalysis: 3000, audioAnalysis: 4000, dbOperation: 1000, pageLoad: 5000 }; return thresholds[name] && value > thresholds[name]; } /** * Get performance report */ getPerformanceReport() { const report = { timestamp: new Date().toISOString(), metrics: {}, cacheStats: this.getCacheStats(), memoryStats: this.getMemoryStats(), recommendations: [] }; // Calculate averages and statistics for (const [name, metrics] of this.metrics.entries()) { if (metrics.length > 0) { const values = metrics.map(m => m.value); report.metrics[name] = { count: values.length, average: values.reduce((a, b) => a + b, 0) / values.length, min: Math.min(...values), max: Math.max(...values), latest: values[values.length - 1] }; } } // Generate recommendations report.recommendations = this.generatePerformanceRecommendations(report); return report; } getCacheStats() { return { imageCache: { size: this.imageCache.size, sizeBytes: this.imageCacheSize }, audioCache: { size: this.audioCache.size, sizeBytes: this.audioCacheSize }, dbCache: { size: this.dbCache.size }, generalCache: { size: this.cache.size } }; } getMemoryStats() { if ('memory' in performance) { const memory = performance.memory; return { used: memory.usedJSHeapSize, total: memory.totalJSHeapSize, limit: memory.jsHeapSizeLimit, usagePercent: (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100 }; } return null; } generatePerformanceRecommendations(report) { const recommendations = []; // Check analysis times if (report.metrics.imageAnalysis?.average > 2000) { recommendations.push({ type: 'performance', priority: 'medium', message: 'Image analysis is slow. Consider optimizing image size or using web workers.', metric: 'imageAnalysis' }); } if (report.metrics.audioAnalysis?.average > 3000) { recommendations.push({ type: 'performance', priority: 'medium', message: 'Audio analysis is slow. Consider preprocessing audio or using streaming analysis.', metric: 'audioAnalysis' }); } // Check memory usage const memoryStats = this.getMemoryStats(); if (memoryStats && memoryStats.usagePercent > 80) { recommendations.push({ type: 'memory', priority: 'high', message: 'High memory usage detected. Consider clearing caches or reducing data retention.', metric: 'memory' }); } // Check cache efficiency const cacheStats = this.getCacheStats(); if (cacheStats.imageCache.sizeBytes > this.thresholds.cacheSize * 0.8) { recommendations.push({ type: 'cache', priority: 'low', message: 'Image cache is large. Consider reducing cache size or TTL.', metric: 'cache' }); } return recommendations; } /** * Lazy loading utilities */ observeLazyLoad(element) { if (this.lazyLoadObserver) { this.lazyLoadObserver.observe(element); } } loadLazyContent(element) { // Implementation depends on element type if (element.dataset.src) { element.src = element.dataset.src; element.removeAttribute('data-src'); } if (this.lazyLoadObserver) { this.lazyLoadObserver.unobserve(element); } } /** * Cleanup on destroy */ destroy() { // Clear all caches this.cache.clear(); this.imageCache.clear(); this.audioCache.clear(); this.dbCache.clear(); // Disconnect observers this.observers.forEach(observer => observer.disconnect()); if (this.lazyLoadObserver) { this.lazyLoadObserver.disconnect(); } console.log('⚡ Performance optimizer destroyed'); } } // Create singleton instance export const performanceOptimizer = new PerformanceOptimizer(); // Export optimization functions export const optimizeImageAnalysis = (imageSource, analysisFunction) => { return performanceOptimizer.optimizeImageAnalysis(imageSource, analysisFunction); }; export const optimizeAudioAnalysis = (audioData, analysisFunction) => { return performanceOptimizer.optimizeAudioAnalysis(audioData, analysisFunction); }; export const optimizeDbOperation = (operation, cacheKey) => { return performanceOptimizer.optimizeDbOperation(operation, cacheKey); }; export const getPerformanceReport = () => { return performanceOptimizer.getPerformanceReport(); }; export default performanceOptimizer;