UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

949 lines 38.1 kB
import { EventEmitter } from "events"; import { writeFile, readFile, mkdir, readdir } from "fs/promises"; import { join } from "path"; import { Logger } from "../core/logger.js"; import { ConfigManager } from "../core/config.js"; export class AnalyticsManager extends EventEmitter { metrics = new Map(); dashboards = new Map(); insights = new Map(); models = new Map(); reports = new Map(); analyticsPath; logger; config; configuration; constructor(analyticsPath = "./analytics", logger, config) { super(); this.analyticsPath = analyticsPath; this.logger = logger || new Logger({ level: "info", format: "text", destination: "console" }); this.config = config || ConfigManager.getInstance(); this.configuration = this.getDefaultConfiguration(); } async initialize() { try { await mkdir(this.analyticsPath, { recursive: true }); await mkdir(join(this.analyticsPath, "metrics"), { recursive: true }); await mkdir(join(this.analyticsPath, "dashboards"), { recursive: true }); await mkdir(join(this.analyticsPath, "insights"), { recursive: true }); await mkdir(join(this.analyticsPath, "models"), { recursive: true }); await mkdir(join(this.analyticsPath, "reports"), { recursive: true }); await this.loadConfigurations(); await this.initializeDefaultDashboards(); await this.startMetricsCollection(); this.logger.info("Analytics Manager initialized successfully"); } catch (error) { this.logger.error("Failed to initialize Analytics Manager", { error }); throw error; } } async recordMetric(metric) { const fullMetric = { id: `metric-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date(), ...metric, }; const key = `${metric.category}-${metric.name}`; if (!this.metrics.has(key)) { this.metrics.set(key, []); } const metricArray = this.metrics.get(key); metricArray.push(fullMetric); // Keep only recent metrics in memory (configurable retention) const retentionPeriod = 24 * 60 * 60 * 1000; // 24 hours const cutoff = Date.now() - retentionPeriod; const filteredMetrics = metricArray.filter(m => m.timestamp.getTime() > cutoff); this.metrics.set(key, filteredMetrics); // Persist to disk for longer-term storage await this.persistMetric(fullMetric); this.emit("metric:recorded", fullMetric); // Check for anomalies and generate insights await this.checkForAnomalies(key, fullMetric); } async queryMetrics(query) { const results = {}; for (const metricName of query.metrics) { const key = metricName.includes("-") ? metricName : `*-${metricName}`; const matchingKeys = Array.from(this.metrics.keys()).filter(k => key === "*" || k.includes(key.replace("*-", "")) || k === key); let allMetrics = []; for (const k of matchingKeys) { const keyMetrics = this.metrics.get(k) || []; allMetrics.push(...keyMetrics); } // Filter by time range allMetrics = allMetrics.filter(m => m.timestamp >= query.timeRange.start && m.timestamp <= query.timeRange.end); // Apply filters if (query.filters) { for (const [field, value] of Object.entries(query.filters)) { allMetrics = allMetrics.filter(m => { if (field === "tags") { return Object.entries(value).every(([k, v]) => m.tags[k] === v); } return m[field] === value; }); } } // Group by if specified if (query.groupBy && query.groupBy.length > 0) { const grouped = this.groupMetrics(allMetrics, query.groupBy); for (const [group, metrics] of Object.entries(grouped)) { const aggregated = this.aggregateMetrics(metrics, query.aggregation || "avg"); results[`${metricName}-${group}`] = aggregated; } } else { const aggregated = this.aggregateMetrics(allMetrics, query.aggregation || "avg"); results[metricName] = aggregated; } } return results; } async createDashboard(dashboardData) { const dashboard = { id: `dashboard-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, name: dashboardData.name, description: dashboardData.description, type: dashboardData.type, widgets: dashboardData.widgets.map((widget, index) => ({ id: `widget-${Date.now()}-${index}`, ...widget, })), layout: { columns: 12, rows: 8, grid: true, responsive: true, }, permissions: { viewers: [], editors: [], public: false, ...dashboardData.permissions, }, schedule: { autoRefresh: true, refreshInterval: 30, }, filters: [], createdAt: new Date(), updatedAt: new Date(), createdBy: "system", }; this.dashboards.set(dashboard.id, dashboard); await this.saveDashboard(dashboard); this.emit("dashboard:created", dashboard); this.logger.info(`Dashboard created: ${dashboard.name} (${dashboard.id})`); return dashboard; } async generateInsights(scope = {}) { const insights = []; // Default time range: last 24 hours const timeRange = scope.timeRange || { start: new Date(Date.now() - 24 * 60 * 60 * 1000), end: new Date(), }; // Anomaly detection const anomalies = await this.detectAnomalies(timeRange, scope.metrics); insights.push(...anomalies); // Trend analysis const trends = await this.analyzeTrends(timeRange, scope.metrics); insights.push(...trends); // Performance insights const performance = await this.analyzePerformance(timeRange); insights.push(...performance); // Usage insights const usage = await this.analyzeUsage(timeRange); insights.push(...usage); // Cost optimization insights const costOptimizations = await this.analyzeCostOptimization(timeRange); insights.push(...costOptimizations); // Store insights for (const insight of insights) { this.insights.set(insight.id, insight); await this.saveInsight(insight); } this.emit("insights:generated", { insights, scope }); this.logger.info(`Generated ${insights.length} insights`); return insights; } async trainPredictiveModel(modelConfig) { const model = { id: `model-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, name: modelConfig.name, description: modelConfig.description, type: modelConfig.type, algorithm: modelConfig.algorithm, features: modelConfig.features, target: modelConfig.target, accuracy: 0, confidence: 0, trainedAt: new Date(), trainingData: { samples: 0, features: modelConfig.features.length, timeRange: modelConfig.trainingPeriod, }, performance: { precision: 0, recall: 0, f1Score: 0, }, predictions: [], status: "training", }; try { // Collect training data const trainingData = await this.collectTrainingData(model); // Train the model (simplified implementation) const trained = await this.executeModelTraining(model, trainingData); Object.assign(model, trained); model.status = "ready"; this.models.set(model.id, model); await this.saveModel(model); this.emit("model:trained", model); this.logger.info(`Predictive model trained: ${model.name} (${model.id}) - Accuracy: ${model.accuracy}%`); } catch (error) { model.status = "error"; this.logger.error(`Model training failed: ${model.name}`, { error }); throw error; } return model; } async makePrediction(modelId, input) { const model = this.models.get(modelId); if (!model) { throw new Error(`Model not found: ${modelId}`); } if (model.status !== "ready") { throw new Error(`Model is not ready for predictions: ${model.status}`); } // Simple prediction logic (would be replaced with actual ML inference) const prediction = await this.executePrediction(model, input); const result = { id: `prediction-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, modelId, input, prediction: prediction.value, confidence: prediction.confidence, timestamp: new Date(), }; model.predictions.push(result); model.lastPrediction = new Date(); await this.saveModel(model); this.emit("prediction:made", { model, result }); this.logger.debug(`Prediction made: ${modelId} - ${JSON.stringify(result.prediction)}`); return result; } async getPerformanceMetrics(timeRange) { const range = timeRange || { start: new Date(Date.now() - 60 * 60 * 1000), // Last hour end: new Date(), }; const systemMetrics = await this.queryMetrics({ metrics: ["cpu-usage", "memory-usage", "disk-usage", "network-io"], timeRange: range, aggregation: "avg", }); const appMetrics = await this.queryMetrics({ metrics: ["response-time", "request-rate", "error-rate", "uptime"], timeRange: range, aggregation: "avg", }); const dbMetrics = await this.queryMetrics({ metrics: ["db-connections", "query-time", "db-size"], timeRange: range, aggregation: "avg", }); // Construct performance metrics (simplified) return { system: { cpu: { usage: this.getLatestValue(systemMetrics["cpu-usage"]) || 0, cores: 8, // Would be detected from system loadAverage: [1.2, 1.5, 1.8], // Would be collected from system }, memory: { used: this.getLatestValue(systemMetrics["memory-usage"]) || 0, free: 4000000000, // Would be calculated total: 8000000000, usage: 50, }, disk: { used: this.getLatestValue(systemMetrics["disk-usage"]) || 0, free: 100000000000, total: 500000000000, usage: 20, iops: 1000, }, network: { bytesIn: 1000000, bytesOut: 2000000, packetsIn: 5000, packetsOut: 6000, errors: 5, }, }, application: { responseTime: { avg: this.getLatestValue(appMetrics["response-time"]) || 0, p50: 150, p95: 500, p99: 1000, }, throughput: { requestsPerSecond: this.getLatestValue(appMetrics["request-rate"]) || 0, transactionsPerSecond: 50, }, errors: { rate: this.getLatestValue(appMetrics["error-rate"]) || 0, count: 10, types: { "500": 5, "404": 3, "400": 2 }, }, availability: { uptime: this.getLatestValue(appMetrics["uptime"]) || 0, sla: 99.9, incidents: 2, }, }, database: { connections: { active: 25, idle: 75, max: 100, }, queries: { avgExecutionTime: this.getLatestValue(dbMetrics["query-time"]) || 0, slowQueries: 5, deadlocks: 0, }, storage: { size: this.getLatestValue(dbMetrics["db-size"]) || 0, growth: 1000000, // bytes per day fragmentation: 5, }, }, infrastructure: { containers: { running: 12, stopped: 2, restarts: 3, }, services: { healthy: 15, unhealthy: 1, degraded: 0, }, }, }; } async getUsageMetrics(timeRange) { const range = timeRange || { start: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours end: new Date(), }; const usageData = await this.queryMetrics({ metrics: ["active-users", "sessions", "api-calls", "feature-usage"], timeRange: range, aggregation: "sum", }); return { users: { total: 10000, active: this.getLatestValue(usageData["active-users"]) || 0, new: 50, returning: 1500, churn: 25, }, sessions: { total: this.getLatestValue(usageData["sessions"]) || 0, duration: { avg: 15 * 60, // 15 minutes median: 12 * 60, }, bounceRate: 25, pagesPerSession: 4.5, }, features: { adoption: { "dashboard": { users: 800, usage: 5000, retention: 85 }, "reports": { users: 600, usage: 2000, retention: 70 }, "analytics": { users: 400, usage: 1500, retention: 60 }, }, mostUsed: ["dashboard", "reports", "search"], leastUsed: ["advanced-filters", "export", "integrations"], }, api: { calls: this.getLatestValue(usageData["api-calls"]) || 0, uniqueConsumers: 150, avgResponseTime: 250, errorRate: 2.5, rateLimits: { hit: 5, consumed: 75, }, }, content: { created: 100, modified: 250, deleted: 25, views: 5000, }, }; } async getBusinessMetrics(timeRange) { // This would integrate with business systems (CRM, billing, etc.) return { revenue: { total: 1000000, recurring: 800000, growth: 15, arpu: 100, ltv: 2400, }, customers: { total: 500, new: 25, retained: 450, churned: 10, satisfaction: 4.2, }, conversion: { leads: 1000, qualified: 400, opportunities: 200, closed: 50, rate: 5, }, support: { tickets: 150, resolved: 140, avgResolutionTime: 4 * 60 * 60, // 4 hours satisfaction: 4.5, }, }; } // Private helper methods getDefaultConfiguration() { return { collection: { enabled: true, samplingRate: 1.0, batchSize: 1000, flushInterval: 60000, }, storage: { retention: { raw: "7d", aggregated: "90d", summary: "1y", }, compression: true, encryption: false, }, processing: { realTime: true, batchProcessing: true, aggregationIntervals: ["1m", "5m", "1h", "1d"], }, alerts: { enabled: true, channels: ["email", "slack"], escalation: { levels: 3, intervals: [5, 15, 30], // minutes }, }, privacy: { anonymization: true, gdprCompliant: true, dataMinimization: true, }, integrations: {}, }; } async loadConfigurations() { try { // Load dashboards const dashboardFiles = await readdir(join(this.analyticsPath, "dashboards")); for (const file of dashboardFiles.filter(f => f.endsWith(".json"))) { const content = await readFile(join(this.analyticsPath, "dashboards", file), "utf-8"); const dashboard = JSON.parse(content); this.dashboards.set(dashboard.id, dashboard); } // Load insights const insightFiles = await readdir(join(this.analyticsPath, "insights")); for (const file of insightFiles.filter(f => f.endsWith(".json"))) { const content = await readFile(join(this.analyticsPath, "insights", file), "utf-8"); const insight = JSON.parse(content); this.insights.set(insight.id, insight); } // Load models const modelFiles = await readdir(join(this.analyticsPath, "models")); for (const file of modelFiles.filter(f => f.endsWith(".json"))) { const content = await readFile(join(this.analyticsPath, "models", file), "utf-8"); const model = JSON.parse(content); this.models.set(model.id, model); } this.logger.info(`Loaded ${this.dashboards.size} dashboards, ${this.insights.size} insights, ${this.models.size} models`); } catch (error) { this.logger.warn("Failed to load some analytics configurations", { error }); } } async initializeDefaultDashboards() { const defaultDashboards = [ { name: "System Performance", description: "Real-time system performance metrics", type: "operational", widgets: [ { title: "CPU Usage", type: "gauge", size: "medium", position: { x: 0, y: 0, width: 6, height: 3 }, dataSource: { query: "cpu-usage", metrics: ["cpu-usage"], aggregation: "avg", timeRange: "1h", groupBy: [], }, visualization: { chartType: "pie", options: { max: 100, unit: "%" }, thresholds: { warning: 70, critical: 90 }, }, alerts: { enabled: true, conditions: [] }, }, { title: "Memory Usage", type: "gauge", size: "medium", position: { x: 6, y: 0, width: 6, height: 3 }, dataSource: { query: "memory-usage", metrics: ["memory-usage"], aggregation: "avg", timeRange: "1h", groupBy: [], }, visualization: { chartType: "pie", options: { max: 100, unit: "%" }, thresholds: { warning: 80, critical: 95 }, }, alerts: { enabled: true, conditions: [] }, }, { title: "Response Time", type: "chart", size: "large", position: { x: 0, y: 3, width: 12, height: 4 }, dataSource: { query: "response-time", metrics: ["response-time"], aggregation: "avg", timeRange: "24h", groupBy: ["service"], }, visualization: { chartType: "line", options: { unit: "ms" }, }, alerts: { enabled: false, conditions: [] }, }, ], }, { name: "Business KPIs", description: "Key business performance indicators", type: "executive", widgets: [ { title: "Active Users", type: "metric", size: "small", position: { x: 0, y: 0, width: 3, height: 2 }, dataSource: { query: "active-users", metrics: ["active-users"], aggregation: "count", timeRange: "24h", groupBy: [], }, visualization: { options: { unit: "users" }, }, alerts: { enabled: false, conditions: [] }, }, ], }, ]; for (const dashboardData of defaultDashboards) { if (!Array.from(this.dashboards.values()).some(d => d.name === dashboardData.name)) { await this.createDashboard(dashboardData); } } } async startMetricsCollection() { // Start collecting system metrics setInterval(async () => { await this.collectSystemMetrics(); }, 60000); // Every minute // Start collecting application metrics setInterval(async () => { await this.collectApplicationMetrics(); }, 30000); // Every 30 seconds this.logger.info("Started automatic metrics collection"); } async collectSystemMetrics() { try { // Mock system metrics collection await this.recordMetric({ name: "cpu-usage", description: "CPU usage percentage", type: "gauge", category: "performance", unit: "percent", value: Math.random() * 100, tags: { host: "localhost", service: "system" }, source: "system-monitor", metadata: {}, }); await this.recordMetric({ name: "memory-usage", description: "Memory usage percentage", type: "gauge", category: "performance", unit: "percent", value: Math.random() * 100, tags: { host: "localhost", service: "system" }, source: "system-monitor", metadata: {}, }); await this.recordMetric({ name: "disk-usage", description: "Disk usage percentage", type: "gauge", category: "performance", unit: "percent", value: Math.random() * 100, tags: { host: "localhost", service: "system" }, source: "system-monitor", metadata: {}, }); } catch (error) { this.logger.error("Failed to collect system metrics", { error }); } } async collectApplicationMetrics() { try { // Mock application metrics collection await this.recordMetric({ name: "response-time", description: "Average response time", type: "gauge", category: "performance", unit: "milliseconds", value: Math.random() * 1000 + 100, tags: { service: "api", endpoint: "/users" }, source: "application", metadata: {}, }); await this.recordMetric({ name: "request-rate", description: "Requests per second", type: "counter", category: "usage", unit: "requests/sec", value: Math.random() * 100 + 10, tags: { service: "api" }, source: "application", metadata: {}, }); await this.recordMetric({ name: "error-rate", description: "Error rate percentage", type: "gauge", category: "performance", unit: "percent", value: Math.random() * 5, tags: { service: "api" }, source: "application", metadata: {}, }); } catch (error) { this.logger.error("Failed to collect application metrics", { error }); } } async persistMetric(metric) { const date = metric.timestamp.toISOString().split("T")[0]; const filePath = join(this.analyticsPath, "metrics", `${date}.json`); try { let existingData = []; try { const content = await readFile(filePath, "utf-8"); existingData = JSON.parse(content); } catch { // File doesn't exist yet } existingData.push(metric); await writeFile(filePath, JSON.stringify(existingData, null, 2)); } catch (error) { this.logger.error("Failed to persist metric", { error, metric: metric.id }); } } async checkForAnomalies(metricKey, metric) { const historical = this.metrics.get(metricKey) || []; if (historical.length < 10) return; // Need enough data for baseline const recent = historical.slice(-10); const average = recent.reduce((sum, m) => sum + m.value, 0) / recent.length; const stdDev = Math.sqrt(recent.reduce((sum, m) => sum + Math.pow(m.value - average, 2), 0) / recent.length); const threshold = 2; // 2 standard deviations const deviation = Math.abs(metric.value - average) / stdDev; if (deviation > threshold) { const insight = { id: `insight-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, title: `Anomaly detected in ${metric.name}`, description: `The metric ${metric.name} has deviated significantly from its normal pattern`, type: "anomaly", category: metric.category === "business" || metric.category === "technical" ? "performance" : metric.category, confidence: Math.min(95, deviation * 20), impact: deviation > 3 ? "high" : "medium", priority: deviation > 3 ? "high" : "medium", data: { metrics: [metric.name], timeRange: { start: recent[0].timestamp, end: metric.timestamp }, values: { current: metric.value, average, stdDev }, baseline: { average, stdDev }, deviation, }, recommendations: [ { action: "Investigate the cause of the anomaly", effort: "medium", impact: "Identify potential issues before they become critical", implementation: [ "Check recent deployments or configuration changes", "Review system logs for errors or warnings", "Monitor related metrics for correlation", ], }, ], status: "new", createdAt: new Date(), updatedAt: new Date(), }; this.insights.set(insight.id, insight); await this.saveInsight(insight); this.emit("anomaly:detected", { metric, insight, deviation }); this.logger.warn(`Anomaly detected in ${metric.name}`, { current: metric.value, average, deviation, }); } } async detectAnomalies(timeRange, metrics) { // Simplified anomaly detection return []; } async analyzeTrends(timeRange, metrics) { // Simplified trend analysis return []; } async analyzePerformance(timeRange) { const insights = []; // Check response time trends const responseTimeData = await this.queryMetrics({ metrics: ["response-time"], timeRange, aggregation: "avg", }); if (responseTimeData["response-time"]?.length > 0) { const values = responseTimeData["response-time"].map((d) => d.value); const recent = values.slice(-5); const earlier = values.slice(0, -5); if (recent.length > 0 && earlier.length > 0) { const recentAvg = recent.reduce((sum, v) => sum + v, 0) / recent.length; const earlierAvg = earlier.reduce((sum, v) => sum + v, 0) / earlier.length; const change = ((recentAvg - earlierAvg) / earlierAvg) * 100; if (Math.abs(change) > 20) { insights.push({ id: `perf-insight-${Date.now()}`, title: `Response time ${change > 0 ? "increased" : "decreased"} by ${Math.abs(change).toFixed(1)}%`, description: "Response time has changed significantly in the recent period", type: "trend", category: "performance", confidence: 80, impact: Math.abs(change) > 50 ? "high" : "medium", priority: Math.abs(change) > 50 ? "high" : "medium", data: { metrics: ["response-time"], timeRange, values: { recent: recentAvg, earlier: earlierAvg, change }, }, recommendations: change > 0 ? [ { action: "Investigate performance degradation", effort: "medium", impact: "Restore optimal response times", implementation: [ "Check for increased load or traffic", "Review recent code deployments", "Analyze database query performance", "Monitor resource utilization", ], }, ] : [ { action: "Document performance improvement", effort: "low", impact: "Understand what caused the improvement", implementation: [ "Identify recent optimizations", "Document best practices", "Monitor sustainability", ], }, ], status: "new", createdAt: new Date(), updatedAt: new Date(), }); } } } return insights; } async analyzeUsage(timeRange) { // Simplified usage analysis return []; } async analyzeCostOptimization(timeRange) { // Simplified cost optimization analysis return []; } async collectTrainingData(model) { // Collect historical data for training const data = await this.queryMetrics({ metrics: model.features, timeRange: model.trainingData.timeRange, aggregation: "avg", }); // Transform data for ML training (simplified) return Object.values(data).flat(); } async executeModelTraining(model, data) { // Simplified model training return { accuracy: 85 + Math.random() * 10, confidence: 80 + Math.random() * 15, performance: { precision: 0.85, recall: 0.82, f1Score: 0.83, }, trainingData: { ...model.trainingData, samples: data.length, }, }; } async executePrediction(model, input) { // Simplified prediction logic const value = Math.random() * 100; const confidence = 70 + Math.random() * 25; return { value, confidence }; } groupMetrics(metrics, groupBy) { const groups = {}; for (const metric of metrics) { const key = groupBy.map(field => { if (field === "tags") { return Object.entries(metric.tags).map(([k, v]) => `${k}:${v}`).join(","); } return metric[field] || "unknown"; }).join("-"); if (!groups[key]) { groups[key] = []; } groups[key].push(metric); } return groups; } aggregateMetrics(metrics, aggregation) { if (metrics.length === 0) return []; // Group by time buckets for time series aggregation const buckets = {}; for (const metric of metrics) { const bucket = new Date(Math.floor(metric.timestamp.getTime() / 60000) * 60000).toISOString(); if (!buckets[bucket]) { buckets[bucket] = []; } buckets[bucket].push(metric); } return Object.entries(buckets).map(([timestamp, bucketMetrics]) => { const values = bucketMetrics.map(m => m.value); let aggregatedValue; switch (aggregation) { case "sum": aggregatedValue = values.reduce((sum, v) => sum + v, 0); break; case "min": aggregatedValue = Math.min(...values); break; case "max": aggregatedValue = Math.max(...values); break; case "count": aggregatedValue = values.length; break; case "p95": values.sort((a, b) => a - b); aggregatedValue = values[Math.floor(values.length * 0.95)]; break; case "p99": values.sort((a, b) => a - b); aggregatedValue = values[Math.floor(values.length * 0.99)]; break; case "avg": default: aggregatedValue = values.reduce((sum, v) => sum + v, 0) / values.length; } return { timestamp: new Date(timestamp), value: aggregatedValue, count: values.length, }; }).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); } getLatestValue(dataPoints) { if (!dataPoints || dataPoints.length === 0) return 0; return dataPoints[dataPoints.length - 1]?.value || 0; } async saveDashboard(dashboard) { const filePath = join(this.analyticsPath, "dashboards", `${dashboard.id}.json`); await writeFile(filePath, JSON.stringify(dashboard, null, 2)); } async saveInsight(insight) { const filePath = join(this.analyticsPath, "insights", `${insight.id}.json`); await writeFile(filePath, JSON.stringify(insight, null, 2)); } async saveModel(model) { const filePath = join(this.analyticsPath, "models", `${model.id}.json`); await writeFile(filePath, JSON.stringify(model, null, 2)); } } //# sourceMappingURL=analytics-manager.js.map