GreenPlusbyGXS / web /src /utils /performanceOptimizer.js
gaialive's picture
Upload 106 files
759768a verified
/**
* 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;