/** * Performance Metrics Collection and Analysis * * Comprehensive performance metrics collection for zanzibar-graph * performance testing with statistical analysis and reporting. */ export class PerformanceMetrics { constructor() { this.metrics = new Map(); this.startTime = Date.now(); this.testCount = 0; } /** * Record a performance metric */ record(testName, data) { if (!this.metrics.has(testName)) { this.metrics.set(testName, []); } const metric = { timestamp: Date.now(), testName, data, testId: ++this.testCount }; this.metrics.get(testName).push(metric); } /** * Get metrics for a specific test */ getMetrics(testName) { return this.metrics.get(testName) || []; } /** * Get all metrics */ getAllMetrics() { const allMetrics = {}; for (const [testName, metrics] of this.metrics) { allMetrics[testName] = metrics; } return allMetrics; } /** * Calculate statistical summary for a test */ calculateSummary(testName) { const metrics = this.getMetrics(testName); if (metrics.length === 0) return null; const values = metrics.map(m => m.data); // Extract numeric values for statistical analysis const numericValues = this._extractNumericValues(values); if (numericValues.length === 0) return null; return { count: numericValues.length, min: Math.min(...numericValues), max: Math.max(...numericValues), mean: this._calculateMean(numericValues), median: this._calculateMedian(numericValues), p95: this._calculatePercentile(numericValues, 95), p99: this._calculatePercentile(numericValues, 99), stdDev: this._calculateStdDev(numericValues), variance: this._calculateVariance(numericValues) }; } /** * Generate comprehensive performance report */ generateReport() { const report = { summary: this._generateSummary(), testResults: {}, performanceTargets: this._getPerformanceTargets(), recommendations: this._generateRecommendations(), generatedAt: new Date().toISOString(), duration: Date.now() - this.startTime }; // Generate test-specific results for (const [testName, metrics] of this.metrics) { report.testResults[testName] = { summary: this.calculateSummary(testName), rawData: metrics, analysis: this._analyzeTest(testName, metrics) }; } return report; } /** * Generate overall summary */ _generateSummary() { const allMetrics = this.getAllMetrics(); const totalTests = Object.keys(allMetrics).length; // Calculate overall performance metrics let totalLatency = 0; let totalMemory = 0; let totalQueries = 0; let testCount = 0; for (const [testName, metrics] of Object.entries(allMetrics)) { for (const metric of metrics) { if (metric.data.avgLatency) { totalLatency += metric.data.avgLatency; testCount++; } if (metric.data.memoryUsed) { totalMemory += metric.data.memoryUsed; } if (metric.data.queryCount) { totalQueries += metric.data.queryCount; } } } return { totalTests, avgLatency: testCount > 0 ? totalLatency / testCount : 0, totalMemoryUsage: totalMemory, totalQueries, testCount, duration: Date.now() - this.startTime }; } /** * Get performance targets */ _getPerformanceTargets() { return { latency: { avg: 100, // ms p95: 200, // ms p99: 500 // ms }, memory: { small: 512, // MB medium: 1024, // MB large: 2048, // MB enterprise: 4096 // MB }, throughput: { qps: 1000, // queries per second tps: 500 // transactions per second }, cache: { hitRate: 0.8, // 80% evictionRate: 0.1 // 10% } }; } /** * Generate performance recommendations */ _generateRecommendations() { const recommendations = []; const summary = this._generateSummary(); const targets = this._getPerformanceTargets(); // Latency recommendations if (summary.avgLatency > targets.latency.avg) { recommendations.push({ type: 'latency', severity: 'high', message: `Average latency ${summary.avgLatency}ms exceeds target ${targets.latency.avg}ms`, suggestion: 'Consider optimizing authorization logic or increasing cache size' }); } // Memory recommendations if (summary.totalMemoryUsage > targets.memory.medium) { recommendations.push({ type: 'memory', severity: 'medium', message: `Memory usage ${summary.totalMemoryUsage}MB exceeds target ${targets.memory.medium}MB`, suggestion: 'Consider implementing memory optimization or increasing heap size' }); } // Throughput recommendations if (summary.totalQueries > 0) { const avgQPS = (summary.totalQueries / summary.duration) * 1000; if (avgQPS < targets.throughput.qps * 0.8) { recommendations.push({ type: 'throughput', severity: 'medium', message: `Average QPS ${avgQPS} below target ${targets.throughput.qps}`, suggestion: 'Consider optimizing query performance or increasing concurrency' }); } } return recommendations; } /** * Analyze specific test results */ _analyzeTest(testName, metrics) { const analysis = { performance: 'good', issues: [], suggestions: [] }; // Analyze based on test type switch (testName) { case 'graph_loading': this._analyzeGraphLoading(metrics, analysis); break; case 'authorization_qps': this._analyzeAuthorizationQPS(metrics, analysis); break; case 'memory_leak_test': this._analyzeMemoryLeak(metrics, analysis); break; case 'cache_performance': this._analyzeCachePerformance(metrics, analysis); break; default: this._analyzeGeneric(metrics, analysis); } return analysis; } /** * Analyze graph loading performance */ _analyzeGraphLoading(metrics, analysis) { for (const metric of metrics) { const data = metric.data; if (data.loadTime > 30000) { analysis.issues.push('Graph loading time exceeds 30s limit'); analysis.suggestions.push('Consider optimizing graph construction or using lazy loading'); analysis.performance = 'poor'; } if (data.memoryUsed > 1024) { analysis.issues.push('Memory usage exceeds 1GB limit'); analysis.suggestions.push('Consider implementing memory optimization or reducing graph size'); analysis.performance = 'poor'; } } } /** * Analyze authorization QPS performance */ _analyzeAuthorizationQPS(metrics, analysis) { for (const metric of metrics) { const data = metric.data; if (data.actualQPS < data.targetQPS * 0.8) { analysis.issues.push(`QPS ${data.actualQPS} below 80% of target ${data.targetQPS}`); analysis.suggestions.push('Consider optimizing authorization logic or increasing concurrency'); analysis.performance = 'poor'; } if (data.avgLatency > 100) { analysis.issues.push(`Average latency ${data.avgLatency}ms exceeds 100ms limit`); analysis.suggestions.push('Consider optimizing query performance or increasing cache size'); analysis.performance = 'poor'; } if (data.p95Latency > 200) { analysis.issues.push(`P95 latency ${data.p95Latency}ms exceeds 200ms limit`); analysis.suggestions.push('Consider optimizing worst-case performance or reducing query complexity'); analysis.performance = 'poor'; } } } /** * Analyze memory leak test results */ _analyzeMemoryLeak(metrics, analysis) { for (const metric of metrics) { const data = metric.data; if (data.totalGrowth > 200) { analysis.issues.push(`Memory growth ${data.totalGrowth}MB exceeds 200MB limit`); analysis.suggestions.push('Investigate potential memory leaks in authorization logic'); analysis.performance = 'poor'; } if (data.totalGrowth > 100) { analysis.issues.push(`Memory growth ${data.totalGrowth}MB exceeds 100MB limit`); analysis.suggestions.push('Monitor memory usage and consider implementing garbage collection'); analysis.performance = 'fair'; } } } /** * Analyze cache performance */ _analyzeCachePerformance(metrics, analysis) { for (const metric of metrics) { const data = metric.data; if (data.speedup < 1.5) { analysis.issues.push(`Cache speedup ${data.speedup}x below 1.5x threshold`); analysis.suggestions.push('Consider optimizing cache implementation or increasing cache size'); analysis.performance = 'poor'; } if (data.speedup < 2.0) { analysis.issues.push(`Cache speedup ${data.speedup}x below 2.0x threshold`); analysis.suggestions.push('Consider optimizing cache hit rate or cache eviction strategy'); analysis.performance = 'fair'; } } } /** * Generic analysis for unknown test types */ _analyzeGeneric(metrics, analysis) { const summary = this.calculateSummary(metrics[0]?.testName); if (!summary) return; if (summary.mean > 1000) { analysis.issues.push(`Average performance ${summary.mean}ms exceeds 1000ms threshold`); analysis.suggestions.push('Consider optimizing performance or reducing complexity'); analysis.performance = 'poor'; } } /** * Extract numeric values from metric data */ _extractNumericValues(values) { const numericValues = []; for (const value of values) { if (typeof value === 'number') { numericValues.push(value); } else if (typeof value === 'object' && value !== null) { // Extract numeric values from objects for (const [key, val] of Object.entries(value)) { if (typeof val === 'number') { numericValues.push(val); } } } } return numericValues; } /** * Calculate mean */ _calculateMean(values) { return values.reduce((sum, val) => sum + val, 0) / values.length; } /** * Calculate median */ _calculateMedian(values) { const sorted = [...values].sort((a, b) => a - b); const mid = Math.floor(sorted.length / 2); return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid]; } /** * Calculate percentile */ _calculatePercentile(values, percentile) { const sorted = [...values].sort((a, b) => a - b); const index = Math.ceil((percentile / 100) * sorted.length) - 1; return sorted[Math.max(0, index)]; } /** * Calculate standard deviation */ _calculateStdDev(values) { const mean = this._calculateMean(values); const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length; return Math.sqrt(variance); } /** * Calculate variance */ _calculateVariance(values) { const mean = this._calculateMean(values); return values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length; } /** * Export metrics to JSON */ exportToJSON() { return JSON.stringify(this.generateReport(), null, 2); } /** * Export metrics to CSV */ exportToCSV() { const csv = []; csv.push('TestName,Timestamp,TestId,Data'); for (const [testName, metrics] of this.metrics) { for (const metric of metrics) { csv.push(`${testName},${metric.timestamp},${metric.testId},"${JSON.stringify(metric.data)}"`); } } return csv.join('\n'); } /** * Clear all metrics */ clear() { this.metrics.clear(); this.startTime = Date.now(); this.testCount = 0; } /** * Get metrics for a specific time range */ getMetricsInRange(startTime, endTime) { const filteredMetrics = new Map(); for (const [testName, metrics] of this.metrics) { const filtered = metrics.filter(m => m.timestamp >= startTime && m.timestamp <= endTime ); if (filtered.length > 0) { filteredMetrics.set(testName, filtered); } } return filteredMetrics; } /** * Get performance trends over time */ getPerformanceTrends(testName, windowSize = 1000) { const metrics = this.getMetrics(testName); if (metrics.length === 0) return null; const trends = []; const window = Math.min(windowSize, metrics.length); for (let i = window; i <= metrics.length; i++) { const windowMetrics = metrics.slice(i - window, i); const trend = this._calculateTrend(windowMetrics); trends.push({ timestamp: metrics[i - 1].timestamp, trend: trend }); } return trends; } /** * Calculate trend for a window of metrics */ _calculateTrend(metrics) { if (metrics.length < 2) return null; const values = this._extractNumericValues(metrics.map(m => m.data)); if (values.length < 2) return null; const firstHalf = values.slice(0, Math.floor(values.length / 2)); const secondHalf = values.slice(Math.floor(values.length / 2)); const firstMean = this._calculateMean(firstHalf); const secondMean = this._calculateMean(secondHalf); return { direction: secondMean > firstMean ? 'increasing' : 'decreasing', change: secondMean - firstMean, changePercent: ((secondMean - firstMean) / firstMean) * 100 }; } }