UNPKG

optivise

Version:

Optivise - The Ultimate Optimizely Development Assistant with AI-powered features, zero-config setup, and comprehensive development support

503 lines 18.1 kB
/** * Comprehensive Monitoring and Analytics Service * Tracks performance, usage patterns, and system health with real-time analytics */ import { EventEmitter } from 'events'; export class MonitoringService extends EventEmitter { performanceMetrics = []; usageMetrics = []; systemMetrics = []; alertRules = new Map(); activeAlerts = new Set(); logger; metricsRetentionMs; cleanupInterval; constructor(logger, retentionHours = 24) { super(); this.logger = logger; this.metricsRetentionMs = retentionHours * 60 * 60 * 1000; this.setupDefaultAlerts(); this.startCleanupTimer(); this.logger.info('Monitoring Service initialized', { retentionHours, defaultAlerts: this.alertRules.size }); } /** * Record performance metric */ recordPerformance(metric) { const fullMetric = { ...metric, timestamp: Date.now() }; this.performanceMetrics.push(fullMetric); this.emit('performance', fullMetric); // Check for performance alerts this.checkPerformanceAlerts(fullMetric); } /** * Record usage metric */ recordUsage(metric) { const fullMetric = { ...metric, timestamp: Date.now() }; this.usageMetrics.push(fullMetric); this.emit('usage', fullMetric); } /** * Record system metric */ recordSystem(metric) { const fullMetric = { ...metric, timestamp: Date.now() }; this.systemMetrics.push(fullMetric); this.emit('system', fullMetric); // Check for system alerts this.checkSystemAlerts(fullMetric); } /** * Start timing a performance operation */ startTimer(operation, metadata) { const start = Date.now(); return (success = true) => { this.recordPerformance({ operation, duration: Date.now() - start, success, metadata }); }; } /** * Decorator for timing async operations */ timeAsync(operation, metadata) { return (target, propertyKey, descriptor) => { const originalMethod = descriptor.value; descriptor.value = async function (...args) { const timer = this.monitoringService?.startTimer?.(operation, metadata) ?? (() => { }); try { const result = await originalMethod.apply(this, args); timer(true); return result; } catch (error) { timer(false); throw error; } }; return descriptor; }; } /** * Add or update alert rule */ addAlertRule(rule) { this.alertRules.set(rule.id, rule); this.logger.debug('Alert rule added', { id: rule.id, name: rule.name }); this.emit('alertRuleAdded', rule); } /** * Remove alert rule */ removeAlertRule(id) { const removed = this.alertRules.delete(id); if (removed) { this.activeAlerts.delete(id); this.logger.debug('Alert rule removed', { id }); this.emit('alertRuleRemoved', { id }); } return removed; } /** * Get all alert rules */ getAlertRules() { return Array.from(this.alertRules.values()); } /** * Get active alerts */ getActiveAlerts() { return Array.from(this.activeAlerts); } /** * Generate comprehensive analytics report */ generateReport(timeframeHours = 24) { const now = Date.now(); const timeframe = { start: now - (timeframeHours * 60 * 60 * 1000), end: now }; const performanceMetrics = this.performanceMetrics.filter(m => m.timestamp >= timeframe.start && m.timestamp <= timeframe.end); const usageMetrics = this.usageMetrics.filter(m => m.timestamp >= timeframe.start && m.timestamp <= timeframe.end); const systemMetrics = this.systemMetrics.filter(m => m.timestamp >= timeframe.start && m.timestamp <= timeframe.end); return { timeframe, performance: this.analyzePerformance(performanceMetrics), usage: this.analyzeUsage(usageMetrics), system: this.analyzeSystem(systemMetrics), trends: this.analyzeTrends(performanceMetrics, usageMetrics, timeframeHours) }; } /** * Get real-time system status */ getSystemStatus() { const now = Date.now(); const last5Min = now - (5 * 60 * 1000); const recentPerformance = this.performanceMetrics.filter(m => m.timestamp >= last5Min); const recentErrors = recentPerformance.filter(m => !m.success).length; const avgResponseTime = recentPerformance.length > 0 ? recentPerformance.reduce((sum, m) => sum + m.duration, 0) / recentPerformance.length : 0; const throughput = recentPerformance.length / 5; // per minute let status = 'healthy'; if (this.activeAlerts.size > 0) { const criticalAlerts = Array.from(this.activeAlerts).some(id => { const rule = this.alertRules.get(id); return rule?.severity === 'critical'; }); status = criticalAlerts ? 'critical' : 'warning'; } return { status, uptime: process.uptime() * 1000, activeAlerts: this.activeAlerts.size, recentErrors, performance: { avgResponseTime, throughput } }; } /** * Export metrics for external analysis */ exportMetrics(format = 'json') { const data = { performance: this.performanceMetrics, usage: this.usageMetrics, system: this.systemMetrics, exportTime: Date.now() }; if (format === 'json') { return JSON.stringify(data, null, 2); } else { // Simple CSV export for performance metrics const headers = 'timestamp,operation,duration,success,metadata\n'; const rows = this.performanceMetrics.map(m => `${m.timestamp},${m.operation},${m.duration},${m.success},"${JSON.stringify(m.metadata || {})}"`).join('\n'); return headers + rows; } } /** * Setup default alert rules */ setupDefaultAlerts() { this.addAlertRule({ id: 'high_response_time', name: 'High Response Time', condition: (metrics) => { const recent = metrics.filter(m => m.metric === 'response_time' && Date.now() - m.timestamp < 5 * 60 * 1000); return recent.length > 0 && recent.reduce((sum, m) => sum + m.value, 0) / recent.length > 2000; }, severity: 'high', enabled: true, cooldown: 300 // 5 minutes }); this.addAlertRule({ id: 'high_error_rate', name: 'High Error Rate', condition: () => { const recent = this.performanceMetrics.filter(m => Date.now() - m.timestamp < 5 * 60 * 1000); if (recent.length < 10) return false; const errorRate = recent.filter(m => !m.success).length / recent.length; return errorRate > 0.1; // 10% error rate }, severity: 'critical', enabled: true, cooldown: 300 }); this.addAlertRule({ id: 'memory_usage', name: 'High Memory Usage', condition: (metrics) => { const recent = metrics.filter(m => m.metric === 'memory_usage' && Date.now() - m.timestamp < 60 * 1000); return recent.length > 0 && Math.max(...recent.map(m => m.value)) > 512 * 1024 * 1024; // 512MB }, severity: 'medium', enabled: true, cooldown: 600 // 10 minutes }); } /** * Check performance alerts */ checkPerformanceAlerts(metric) { // Convert to system metric format for alert checking const systemMetric = { metric: 'response_time', value: metric.duration, unit: 'ms', timestamp: metric.timestamp, tags: { operation: metric.operation } }; this.checkSystemAlerts(systemMetric); } /** * Check system alerts */ checkSystemAlerts(metric) { for (const rule of this.alertRules.values()) { if (!rule.enabled) continue; // Check cooldown period if (rule.lastTriggered && (Date.now() - rule.lastTriggered) < (rule.cooldown * 1000)) { continue; } try { const recentMetrics = this.systemMetrics.filter(m => Date.now() - m.timestamp < 10 * 60 * 1000 // Last 10 minutes ); if (rule.condition(recentMetrics)) { this.triggerAlert(rule); } else { this.resolveAlert(rule.id); } } catch (error) { this.logger.error('Error checking alert rule', error, { ruleId: rule.id }); } } } /** * Trigger an alert */ triggerAlert(rule) { if (!this.activeAlerts.has(rule.id)) { this.activeAlerts.add(rule.id); rule.lastTriggered = Date.now(); this.logger.warn('Alert triggered', { id: rule.id, name: rule.name, severity: rule.severity }); this.emit('alert', { rule, status: 'triggered', timestamp: Date.now() }); } } /** * Resolve an alert */ resolveAlert(ruleId) { if (this.activeAlerts.has(ruleId)) { this.activeAlerts.delete(ruleId); const rule = this.alertRules.get(ruleId); this.logger.info('Alert resolved', { id: ruleId, name: rule?.name }); this.emit('alert', { rule, status: 'resolved', timestamp: Date.now() }); } } /** * Analyze performance metrics */ analyzePerformance(metrics) { if (metrics.length === 0) { return { avgResponseTime: 0, operationsPerSecond: 0, errorRate: 0, slowestOperations: [] }; } const avgResponseTime = metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length; const timeSpan = (metrics[metrics.length - 1]?.timestamp || 0) - (metrics[0]?.timestamp || 0); const operationsPerSecond = timeSpan > 0 ? metrics.length / (timeSpan / 1000) : metrics.length; const errorRate = metrics.filter(m => !m.success).length / metrics.length; // Group by operation and calculate average duration const operationGroups = new Map(); metrics.forEach(m => { if (!operationGroups.has(m.operation)) { operationGroups.set(m.operation, []); } operationGroups.get(m.operation).push(m.duration); }); const slowestOperations = Array.from(operationGroups.entries()) .map(([operation, durations]) => ({ operation, avgDuration: durations.reduce((sum, d) => sum + d, 0) / durations.length })) .sort((a, b) => b.avgDuration - a.avgDuration) .slice(0, 5); return { avgResponseTime, operationsPerSecond, errorRate, slowestOperations }; } /** * Analyze usage metrics */ analyzeUsage(metrics) { const totalRequests = metrics.length; const uniqueUsers = new Set(metrics.map(m => m.user).filter(Boolean)).size; // Top tools by usage const toolCounts = new Map(); metrics.forEach(m => { toolCounts.set(m.tool, (toolCounts.get(m.tool) || 0) + 1); }); const topTools = Array.from(toolCounts.entries()) .map(([tool, usage]) => ({ tool, usage })) .sort((a, b) => b.usage - a.usage) .slice(0, 5); // Top features by usage const featureCounts = new Map(); metrics.forEach(m => { featureCounts.set(m.feature, (featureCounts.get(m.feature) || 0) + 1); }); const topFeatures = Array.from(featureCounts.entries()) .map(([feature, usage]) => ({ feature, usage })) .sort((a, b) => b.usage - a.usage) .slice(0, 5); // Product distribution (from context) const productDistribution = {}; metrics.forEach(m => { const products = m.context?.detectedProducts || []; products.forEach(product => { productDistribution[product] = (productDistribution[product] || 0) + 1; }); }); return { totalRequests, uniqueUsers, topTools, topFeatures, productDistribution }; } /** * Analyze system metrics */ analyzeSystem(metrics) { const getLatestMetric = (metricName) => { const filtered = metrics.filter(m => m.metric === metricName); const lastMetric = filtered[filtered.length - 1]; return lastMetric ? lastMetric.value : 0; }; return { memoryUsage: getLatestMetric('memory_usage'), cpuUsage: getLatestMetric('cpu_usage'), cacheHitRate: getLatestMetric('cache_hit_rate'), aiServiceUptime: getLatestMetric('ai_service_uptime') }; } /** * Analyze trends */ analyzeTrends(performance, usage, timeframeHours) { const hoursArray = new Array(Math.min(timeframeHours, 24)).fill(0); const now = Date.now(); // Hourly usage distribution usage.forEach(m => { const hourIndex = Math.floor((now - m.timestamp) / (60 * 60 * 1000)); if (hourIndex >= 0 && hourIndex < hoursArray.length) { hoursArray[hoursArray.length - 1 - hourIndex]++; } }); // Daily growth (comparing current period to previous period) const halfPoint = now - (timeframeHours * 60 * 60 * 1000 / 2); const recentUsage = usage.filter(m => m.timestamp >= halfPoint).length; const previousUsage = usage.filter(m => m.timestamp < halfPoint).length; const dailyGrowth = previousUsage > 0 ? ((recentUsage - previousUsage) / previousUsage) * 100 : 0; // Performance trend const firstHalf = performance.filter(m => m.timestamp < halfPoint); const secondHalf = performance.filter(m => m.timestamp >= halfPoint); const firstHalfAvg = firstHalf.length > 0 ? firstHalf.reduce((sum, m) => sum + m.duration, 0) / firstHalf.length : 0; const secondHalfAvg = secondHalf.length > 0 ? secondHalf.reduce((sum, m) => sum + m.duration, 0) / secondHalf.length : 0; let performanceTrend = 'stable'; if (firstHalfAvg > 0) { const change = (secondHalfAvg - firstHalfAvg) / firstHalfAvg; if (change < -0.1) performanceTrend = 'improving'; else if (change > 0.1) performanceTrend = 'declining'; } return { hourlyUsage: hoursArray, dailyGrowth, performanceTrend }; } /** * Start cleanup timer to remove old metrics */ startCleanupTimer() { this.cleanupInterval = setInterval(() => { this.cleanupOldMetrics(); }, 60 * 60 * 1000); // Every hour } /** * Remove old metrics beyond retention period */ cleanupOldMetrics() { const cutoff = Date.now() - this.metricsRetentionMs; const beforeCleanup = { performance: this.performanceMetrics.length, usage: this.usageMetrics.length, system: this.systemMetrics.length }; this.performanceMetrics = this.performanceMetrics.filter(m => m.timestamp >= cutoff); this.usageMetrics = this.usageMetrics.filter(m => m.timestamp >= cutoff); this.systemMetrics = this.systemMetrics.filter(m => m.timestamp >= cutoff); const afterCleanup = { performance: this.performanceMetrics.length, usage: this.usageMetrics.length, system: this.systemMetrics.length }; const cleaned = { performance: beforeCleanup.performance - afterCleanup.performance, usage: beforeCleanup.usage - afterCleanup.usage, system: beforeCleanup.system - afterCleanup.system }; const totalCleaned = cleaned.performance + cleaned.usage + cleaned.system; if (totalCleaned > 0) { this.logger.debug('Metrics cleanup completed', { cleaned, remaining: afterCleanup }); } } /** * Cleanup resources */ destroy() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } this.performanceMetrics = []; this.usageMetrics = []; this.systemMetrics = []; this.alertRules.clear(); this.activeAlerts.clear(); this.removeAllListeners(); this.logger.info('Monitoring Service destroyed'); } } // Global monitoring instance export const monitoringService = (logger) => new MonitoringService(logger); //# sourceMappingURL=monitoring-service.js.map