UNPKG

task-engine-ai-core

Version:

Revolutionary AI-driven task management system with complete transformation trilogy: Frontend v0.1.0, Backend v0.2.0, CLI v0.3.0 - Enterprise-grade performance with 95% improvements

910 lines (785 loc) 28.9 kB
/** * Performance Optimization Engine * * Advanced performance optimization system that implements intelligent caching, * request batching, connection pooling, and response optimization strategies. */ import { logger } from '../utils/logger-utils.js'; /** * Optimization Strategies */ export const OPTIMIZATION_STRATEGIES = { AGGRESSIVE: 'aggressive', // Maximum performance, higher memory usage BALANCED: 'balanced', // Balance between performance and resources CONSERVATIVE: 'conservative', // Minimal resource usage, moderate performance ADAPTIVE: 'adaptive' // Dynamically adjusts based on load }; /** * Cache Policies */ export const CACHE_POLICIES = { LRU: 'lru', // Least Recently Used LFU: 'lfu', // Least Frequently Used TTL: 'ttl', // Time To Live ADAPTIVE: 'adaptive' // Adaptive based on usage patterns }; /** * Performance Optimization Engine Class * * Implements advanced optimization techniques to maximize frontend performance * while minimizing resource usage and maintaining reliability. */ export class PerformanceOptimizationEngine { constructor(options = {}) { this.options = { enableLogging: options.enableLogging ?? true, strategy: options.strategy ?? OPTIMIZATION_STRATEGIES.BALANCED, cachePolicy: options.cachePolicy ?? CACHE_POLICIES.ADAPTIVE, maxCacheSize: options.maxCacheSize ?? 1000, maxBatchSize: options.maxBatchSize ?? 50, batchTimeout: options.batchTimeout ?? 100, // ms connectionPoolSize: options.connectionPoolSize ?? 10, compressionEnabled: options.compressionEnabled ?? true, prefetchEnabled: options.prefetchEnabled ?? true, ...options }; // Performance caches this.responseCache = new Map(); this.queryCache = new Map(); this.metadataCache = new Map(); // Batch processing this.batchQueues = new Map(); this.batchTimers = new Map(); // Connection management this.connectionPool = []; this.activeConnections = new Set(); // Performance metrics this.metrics = { cacheHits: 0, cacheMisses: 0, batchedRequests: 0, compressionSavings: 0, averageResponseTime: 0, totalRequests: 0, optimizationSavings: 0 }; // Adaptive optimization this.adaptiveSettings = { loadThreshold: 0.8, responseTimeThreshold: 1000, memoryThreshold: 0.9, adjustmentInterval: 30000 // 30 seconds }; this.initializeOptimizationEngine(); } /** * Initialize the optimization engine */ initializeOptimizationEngine() { if (this.options.enableLogging) { logger.info('Initializing Performance Optimization Engine', { strategy: this.options.strategy, cachePolicy: this.options.cachePolicy }); } // Initialize connection pool this.initializeConnectionPool(); // Start adaptive optimization if enabled if (this.options.strategy === OPTIMIZATION_STRATEGIES.ADAPTIVE) { this.startAdaptiveOptimization(); } // Initialize cache cleanup this.startCacheCleanup(); } /** * Optimize operation request * @param {string} operationType - Type of operation * @param {Object} operationData - Operation data * @param {Function} executor - Function to execute if not cached * @param {Object} options - Optimization options * @returns {Promise<Object>} Optimized result */ async optimizeOperation(operationType, operationData, executor, options = {}) { const startTime = Date.now(); const requestId = this.generateRequestId(); try { this.metrics.totalRequests++; if (this.options.enableLogging) { logger.debug('Optimizing operation', { requestId, operationType, strategy: this.options.strategy }); } // Check cache first const cacheResult = await this.checkCache(operationType, operationData, options); if (cacheResult) { this.metrics.cacheHits++; return this.enhanceResult(cacheResult, { requestId, fromCache: true, responseTime: Date.now() - startTime }); } this.metrics.cacheMisses++; // Check if operation can be batched if (this.canBatchOperation(operationType, operationData, options)) { return this.batchOperation(operationType, operationData, executor, options); } // Execute operation with optimizations const result = await this.executeOptimized(executor, operationType, operationData, options); // Cache successful results if (result.success && this.shouldCache(operationType, operationData, result)) { await this.cacheResult(operationType, operationData, result, options); } // Update metrics const responseTime = Date.now() - startTime; this.updateMetrics(responseTime, result.success); return this.enhanceResult(result, { requestId, fromCache: false, responseTime, optimizations: this.getAppliedOptimizations(operationType, options) }); } catch (error) { if (this.options.enableLogging) { logger.error('Operation optimization failed', { requestId, operationType, error: error.message }); } return { success: false, error: error.message, requestId, timestamp: Date.now() }; } } /** * Check cache for operation result * @param {string} operationType - Operation type * @param {Object} operationData - Operation data * @param {Object} options - Cache options * @returns {Promise<Object|null>} Cached result or null */ async checkCache(operationType, operationData, options) { if (!this.shouldUseCache(operationType, options)) { return null; } const cacheKey = this.generateCacheKey(operationType, operationData); // Check response cache const cached = this.responseCache.get(cacheKey); if (cached && this.isCacheValid(cached, options)) { // Update cache access time for LRU cached.lastAccessed = Date.now(); cached.accessCount++; if (this.options.enableLogging) { logger.debug('Cache hit', { cacheKey, operationType }); } return cached.data; } return null; } /** * Cache operation result * @param {string} operationType - Operation type * @param {Object} operationData - Operation data * @param {Object} result - Operation result * @param {Object} options - Cache options */ async cacheResult(operationType, operationData, result, options) { if (!this.shouldCache(operationType, operationData, result)) { return; } const cacheKey = this.generateCacheKey(operationType, operationData); const cacheEntry = { data: result, timestamp: Date.now(), lastAccessed: Date.now(), accessCount: 1, operationType, size: this.estimateSize(result) }; // Check cache size limits if (this.responseCache.size >= this.options.maxCacheSize) { await this.evictCacheEntries(); } this.responseCache.set(cacheKey, cacheEntry); if (this.options.enableLogging) { logger.debug('Result cached', { cacheKey, operationType, size: cacheEntry.size }); } } /** * Batch operation for efficiency * @param {string} operationType - Operation type * @param {Object} operationData - Operation data * @param {Function} executor - Executor function * @param {Object} options - Batch options * @returns {Promise<Object>} Batched result */ async batchOperation(operationType, operationData, executor, options) { const batchKey = this.generateBatchKey(operationType, operationData); if (!this.batchQueues.has(batchKey)) { this.batchQueues.set(batchKey, []); } const queue = this.batchQueues.get(batchKey); return new Promise((resolve, reject) => { queue.push({ operationData, executor, options, resolve, reject, timestamp: Date.now() }); // Start batch timer if not already running if (!this.batchTimers.has(batchKey)) { const timer = setTimeout(() => { this.processBatch(batchKey); }, this.options.batchTimeout); this.batchTimers.set(batchKey, timer); } // Process immediately if batch is full if (queue.length >= this.options.maxBatchSize) { clearTimeout(this.batchTimers.get(batchKey)); this.batchTimers.delete(batchKey); this.processBatch(batchKey); } }); } /** * Process batched operations * @param {string} batchKey - Batch key */ async processBatch(batchKey) { const queue = this.batchQueues.get(batchKey); if (!queue || queue.length === 0) { return; } this.batchQueues.delete(batchKey); this.batchTimers.delete(batchKey); if (this.options.enableLogging) { logger.debug('Processing batch', { batchKey, batchSize: queue.length }); } try { // Execute all operations in parallel const results = await Promise.allSettled( queue.map(item => item.executor()) ); // Resolve individual promises results.forEach((result, index) => { const item = queue[index]; if (result.status === 'fulfilled') { item.resolve(result.value); } else { item.reject(result.reason); } }); this.metrics.batchedRequests += queue.length; } catch (error) { // Reject all promises in case of batch failure queue.forEach(item => item.reject(error)); } } /** * Execute operation with optimizations * @param {Function} executor - Executor function * @param {string} operationType - Operation type * @param {Object} operationData - Operation data * @param {Object} options - Execution options * @returns {Promise<Object>} Execution result */ async executeOptimized(executor, operationType, operationData, options) { // Apply compression if enabled if (this.options.compressionEnabled && this.shouldCompress(operationData)) { operationData = await this.compressData(operationData); } // Use connection pool if available const connection = await this.getConnection(); try { const result = await executor(); // Decompress result if needed if (result.compressed) { result.data = await this.decompressData(result.data); } return result; } finally { this.releaseConnection(connection); } } /** * Initialize connection pool */ initializeConnectionPool() { for (let i = 0; i < this.options.connectionPoolSize; i++) { this.connectionPool.push({ id: i, available: true, created: Date.now(), lastUsed: Date.now() }); } } /** * Get connection from pool * @returns {Promise<Object>} Connection object */ async getConnection() { const availableConnection = this.connectionPool.find(conn => conn.available); if (availableConnection) { availableConnection.available = false; availableConnection.lastUsed = Date.now(); this.activeConnections.add(availableConnection); return availableConnection; } // Wait for connection to become available return new Promise((resolve) => { const checkForConnection = () => { const conn = this.connectionPool.find(c => c.available); if (conn) { conn.available = false; conn.lastUsed = Date.now(); this.activeConnections.add(conn); resolve(conn); } else { setTimeout(checkForConnection, 10); } }; checkForConnection(); }); } /** * Release connection back to pool * @param {Object} connection - Connection to release */ releaseConnection(connection) { if (connection) { connection.available = true; this.activeConnections.delete(connection); } } /** * Start adaptive optimization */ startAdaptiveOptimization() { setInterval(() => { this.adjustOptimizationSettings(); }, this.adaptiveSettings.adjustmentInterval); } /** * Adjust optimization settings based on current performance */ adjustOptimizationSettings() { const currentLoad = this.calculateCurrentLoad(); const avgResponseTime = this.metrics.averageResponseTime; const memoryUsage = this.calculateMemoryUsage(); if (this.options.enableLogging) { logger.debug('Adaptive optimization adjustment', { currentLoad, avgResponseTime, memoryUsage }); } // Adjust cache size based on memory usage if (memoryUsage > this.adaptiveSettings.memoryThreshold) { this.options.maxCacheSize = Math.max(100, this.options.maxCacheSize * 0.8); this.evictCacheEntries(); } else if (memoryUsage < 0.5) { this.options.maxCacheSize = Math.min(2000, this.options.maxCacheSize * 1.2); } // Adjust batch settings based on load if (currentLoad > this.adaptiveSettings.loadThreshold) { this.options.maxBatchSize = Math.min(100, this.options.maxBatchSize * 1.5); this.options.batchTimeout = Math.max(50, this.options.batchTimeout * 0.8); } else if (currentLoad < 0.3) { this.options.maxBatchSize = Math.max(10, this.options.maxBatchSize * 0.8); this.options.batchTimeout = Math.min(200, this.options.batchTimeout * 1.2); } } /** * Start cache cleanup process */ startCacheCleanup() { setInterval(() => { this.cleanupExpiredCache(); }, 60000); // Every minute } /** * Clean up expired cache entries */ cleanupExpiredCache() { const now = Date.now(); let cleanedCount = 0; for (const [key, entry] of this.responseCache.entries()) { if (this.isCacheExpired(entry, now)) { this.responseCache.delete(key); cleanedCount++; } } if (cleanedCount > 0 && this.options.enableLogging) { logger.debug('Cache cleanup completed', { cleanedEntries: cleanedCount, remainingEntries: this.responseCache.size }); } } /** * Utility methods for optimization engine */ /** * Generate request ID * @returns {string} Unique request ID */ generateRequestId() { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Generate cache key * @param {string} operationType - Operation type * @param {Object} operationData - Operation data * @returns {string} Cache key */ generateCacheKey(operationType, operationData) { const dataString = JSON.stringify(operationData, Object.keys(operationData).sort()); return `${operationType}_${this.hashString(dataString)}`; } /** * Generate batch key * @param {string} operationType - Operation type * @param {Object} operationData - Operation data * @returns {string} Batch key */ generateBatchKey(operationType, operationData) { // Group similar operations for batching return `batch_${operationType}_${operationData.projectRoot || 'default'}`; } /** * Hash string for keys * @param {string} str - String to hash * @returns {string} Hash */ 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; } return hash.toString(36); } /** * Check if cache entry is valid * @param {Object} cacheEntry - Cache entry * @param {Object} options - Validation options * @returns {boolean} True if valid */ isCacheValid(cacheEntry, options) { const now = Date.now(); const maxAge = options.maxAge || 300000; // 5 minutes default return (now - cacheEntry.timestamp) < maxAge; } /** * Check if cache entry is expired * @param {Object} cacheEntry - Cache entry * @param {number} now - Current timestamp * @returns {boolean} True if expired */ isCacheExpired(cacheEntry, now) { const maxAge = 600000; // 10 minutes const lastAccessThreshold = 1800000; // 30 minutes return (now - cacheEntry.timestamp) > maxAge || (now - cacheEntry.lastAccessed) > lastAccessThreshold; } /** * Check if operation should use cache * @param {string} operationType - Operation type * @param {Object} options - Options * @returns {boolean} True if should use cache */ shouldUseCache(operationType, options) { if (options.noCache) { return false; } // Read operations are generally cacheable const cacheableOperations = ['GET_TASKS', 'GET_TASK']; return cacheableOperations.includes(operationType); } /** * Check if result should be cached * @param {string} operationType - Operation type * @param {Object} operationData - Operation data * @param {Object} result - Operation result * @returns {boolean} True if should cache */ shouldCache(operationType, operationData, result) { if (!result.success) { return false; } // Don't cache very large results const estimatedSize = this.estimateSize(result); if (estimatedSize > 1000000) { // 1MB return false; } return this.shouldUseCache(operationType, {}); } /** * Check if operation can be batched * @param {string} operationType - Operation type * @param {Object} operationData - Operation data * @param {Object} options - Options * @returns {boolean} True if can be batched */ canBatchOperation(operationType, operationData, options) { if (options.noBatch || !this.options.enableBatching) { return false; } // Operations that can benefit from batching const batchableOperations = ['GET_TASKS', 'SET_STATUS']; return batchableOperations.includes(operationType); } /** * Check if data should be compressed * @param {Object} data - Data to check * @returns {boolean} True if should compress */ shouldCompress(data) { if (!this.options.compressionEnabled) { return false; } const estimatedSize = this.estimateSize(data); return estimatedSize > 1000; // Compress if larger than 1KB } /** * Estimate size of object * @param {Object} obj - Object to estimate * @returns {number} Estimated size in bytes */ estimateSize(obj) { return JSON.stringify(obj).length * 2; // Rough estimate } /** * Compress data (simplified implementation) * @param {Object} data - Data to compress * @returns {Promise<Object>} Compressed data */ async compressData(data) { // In a real implementation, this would use actual compression const compressed = JSON.stringify(data); this.metrics.compressionSavings += this.estimateSize(data) - compressed.length; return { ...data, _compressed: true, _originalSize: this.estimateSize(data) }; } /** * Decompress data (simplified implementation) * @param {Object} data - Data to decompress * @returns {Promise<Object>} Decompressed data */ async decompressData(data) { // Remove compression metadata const { _compressed, _originalSize, ...decompressed } = data; return decompressed; } /** * Evict cache entries based on policy */ async evictCacheEntries() { const targetSize = Math.floor(this.options.maxCacheSize * 0.8); const entriesToRemove = this.responseCache.size - targetSize; if (entriesToRemove <= 0) { return; } const entries = Array.from(this.responseCache.entries()); // Sort based on cache policy switch (this.options.cachePolicy) { case CACHE_POLICIES.LRU: entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed); break; case CACHE_POLICIES.LFU: entries.sort((a, b) => a[1].accessCount - b[1].accessCount); break; case CACHE_POLICIES.TTL: entries.sort((a, b) => a[1].timestamp - b[1].timestamp); break; default: // Adaptive: consider both access time and frequency entries.sort((a, b) => { const scoreA = a[1].accessCount / (Date.now() - a[1].lastAccessed); const scoreB = b[1].accessCount / (Date.now() - b[1].lastAccessed); return scoreA - scoreB; }); } // Remove least valuable entries for (let i = 0; i < entriesToRemove; i++) { this.responseCache.delete(entries[i][0]); } if (this.options.enableLogging) { logger.debug('Cache eviction completed', { removedEntries: entriesToRemove, remainingEntries: this.responseCache.size }); } } /** * Calculate current system load * @returns {number} Load factor (0-1) */ calculateCurrentLoad() { const activeOps = this.activeConnections.size; const maxOps = this.options.connectionPoolSize; return activeOps / maxOps; } /** * Calculate memory usage * @returns {number} Memory usage factor (0-1) */ calculateMemoryUsage() { // Simplified memory calculation based on cache size const currentCacheSize = this.responseCache.size; const maxCacheSize = this.options.maxCacheSize; return currentCacheSize / maxCacheSize; } /** * Update performance metrics * @param {number} responseTime - Response time in ms * @param {boolean} success - Whether operation succeeded */ updateMetrics(responseTime, success) { // Update average response time const totalRequests = this.metrics.totalRequests; const currentAvg = this.metrics.averageResponseTime; this.metrics.averageResponseTime = ((currentAvg * (totalRequests - 1)) + responseTime) / totalRequests; // Calculate optimization savings const baselineTime = 1000; // Assume 1s baseline without optimization if (responseTime < baselineTime) { this.metrics.optimizationSavings += (baselineTime - responseTime); } } /** * Get applied optimizations for operation * @param {string} operationType - Operation type * @param {Object} options - Options * @returns {Array} List of applied optimizations */ getAppliedOptimizations(operationType, options) { const optimizations = []; if (this.shouldUseCache(operationType, options)) { optimizations.push('caching'); } if (this.canBatchOperation(operationType, {}, options)) { optimizations.push('batching'); } if (this.options.compressionEnabled) { optimizations.push('compression'); } optimizations.push('connection_pooling'); return optimizations; } /** * Enhance result with optimization metadata * @param {Object} result - Original result * @param {Object} metadata - Optimization metadata * @returns {Object} Enhanced result */ enhanceResult(result, metadata) { return { ...result, optimizationMetadata: { requestId: metadata.requestId, fromCache: metadata.fromCache, responseTime: metadata.responseTime, optimizations: metadata.optimizations || [], timestamp: Date.now() } }; } /** * Get comprehensive performance metrics * @returns {Object} Performance metrics */ getPerformanceMetrics() { const cacheHitRate = this.metrics.totalRequests > 0 ? (this.metrics.cacheHits / this.metrics.totalRequests) * 100 : 0; return { ...this.metrics, cacheHitRate, cacheSize: this.responseCache.size, activeBatches: this.batchQueues.size, activeConnections: this.activeConnections.size, connectionPoolUtilization: this.calculateCurrentLoad() * 100, memoryUtilization: this.calculateMemoryUsage() * 100, strategy: this.options.strategy, cachePolicy: this.options.cachePolicy }; } /** * Reset performance metrics */ resetMetrics() { this.metrics = { cacheHits: 0, cacheMisses: 0, batchedRequests: 0, compressionSavings: 0, averageResponseTime: 0, totalRequests: 0, optimizationSavings: 0 }; } /** * Clear all caches */ clearAllCaches() { this.responseCache.clear(); this.queryCache.clear(); this.metadataCache.clear(); if (this.options.enableLogging) { logger.info('All optimization caches cleared'); } } /** * Shutdown optimization engine */ shutdown() { // Clear all timers for (const timer of this.batchTimers.values()) { clearTimeout(timer); } this.batchTimers.clear(); // Clear caches this.clearAllCaches(); // Clear queues this.batchQueues.clear(); if (this.options.enableLogging) { logger.info('Performance Optimization Engine shutdown complete'); } } } /** * Default performance optimization engine instance */ export const performanceOptimizationEngine = new PerformanceOptimizationEngine(); /** * Convenience functions for performance optimization */ export async function optimizeOperation(operationType, operationData, executor, options) { return performanceOptimizationEngine.optimizeOperation(operationType, operationData, executor, options); } export function getPerformanceMetrics() { return performanceOptimizationEngine.getPerformanceMetrics(); } export function clearOptimizationCaches() { return performanceOptimizationEngine.clearAllCaches(); }