UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

417 lines • 15.8 kB
/** * Verbose Metrics Reporter for Detailed Performance Analysis * @description Provides comprehensive detailed metrics and debugging information * for performance optimization analysis during cache sync operations. * * Features: * - Granular timing breakdowns * - Memory usage tracking * - API call statistics * - Database query analysis * - Network latency measurements * - Error rate tracking * * @author Optimizely MCP Server * @version 1.0.0 */ import chalk from 'chalk'; import { performance } from 'perf_hooks'; /** * Verbose Metrics Reporter Class */ export class VerboseMetricsReporter { metrics; memoryInterval; currentPhase; constructor() { this.metrics = { startTime: performance.now(), memorySnapshots: [], apiCalls: [], databaseQueries: [], phases: new Map(), errors: [] }; } /** * Start verbose metrics collection */ start() { // Start memory monitoring this.startMemoryMonitoring(); // Capture initial memory snapshot this.captureMemorySnapshot(); console.log(chalk.dim('šŸ” Verbose metrics collection started')); } /** * Stop metrics collection */ stop() { this.metrics.endTime = performance.now(); if (this.memoryInterval) { clearInterval(this.memoryInterval); this.memoryInterval = undefined; } // Capture final memory snapshot this.captureMemorySnapshot(); } /** * Start monitoring memory usage */ startMemoryMonitoring(intervalMs = 1000) { this.memoryInterval = setInterval(() => { this.captureMemorySnapshot(); }, intervalMs); } /** * Capture memory snapshot */ captureMemorySnapshot() { const memUsage = process.memoryUsage(); this.metrics.memorySnapshots.push({ timestamp: performance.now(), heapUsed: memUsage.heapUsed, heapTotal: memUsage.heapTotal, external: memUsage.external, rss: memUsage.rss }); } /** * Start a new phase */ startPhase(phaseName) { this.currentPhase = phaseName; this.metrics.phases.set(phaseName, { startTime: performance.now(), metrics: [] }); } /** * End current phase */ endPhase(phaseName) { const phase = phaseName || this.currentPhase; if (!phase) return; const phaseData = this.metrics.phases.get(phase); if (phaseData) { phaseData.endTime = performance.now(); } if (phase === this.currentPhase) { this.currentPhase = undefined; } } /** * Record API call */ recordApiCall(endpoint, method, duration, statusCode, size) { this.metrics.apiCalls.push({ endpoint, method, duration, statusCode, timestamp: performance.now(), size }); } /** * Record database query */ recordDatabaseQuery(query, duration, rowCount) { this.metrics.databaseQueries.push({ query: query.substring(0, 100), // Truncate for display duration, rowCount, timestamp: performance.now() }); } /** * Record detailed metric */ recordMetric(metric) { if (this.currentPhase) { const phase = this.metrics.phases.get(this.currentPhase); if (phase) { phase.metrics.push({ ...metric, timestamp: performance.now() }); } } } /** * Record error */ recordError(error, context) { this.metrics.errors.push({ timestamp: performance.now(), phase: this.currentPhase || 'unknown', error, context }); } /** * Generate verbose report */ generateReport() { const totalDuration = (this.metrics.endTime || performance.now()) - this.metrics.startTime; console.log('\n' + chalk.bold.magenta('═'.repeat(80))); console.log(chalk.bold.white.bgMagenta(' VERBOSE PERFORMANCE METRICS ')); console.log(chalk.magenta('═'.repeat(80))); // Overall timing console.log(chalk.bold('\nā±ļø TIMING ANALYSIS:')); console.log(` Total Duration: ${(totalDuration / 1000).toFixed(3)}s`); console.log(` Start Time: ${new Date(Date.now() - totalDuration).toLocaleTimeString()}`); console.log(` End Time: ${new Date().toLocaleTimeString()}`); // Phase breakdown this.generatePhaseBreakdown(); // Memory analysis this.generateMemoryAnalysis(); // API performance this.generateApiAnalysis(); // Database performance this.generateDatabaseAnalysis(); // Error analysis this.generateErrorAnalysis(); // Summary statistics this.generateSummaryStatistics(); console.log('\n' + chalk.magenta('═'.repeat(80)) + '\n'); } /** * Generate phase breakdown */ generatePhaseBreakdown() { console.log(chalk.bold('\nšŸ“Š PHASE BREAKDOWN:')); console.log(chalk.gray('─'.repeat(60))); const phases = Array.from(this.metrics.phases.entries()) .map(([name, data]) => ({ name, duration: (data.endTime || performance.now()) - data.startTime, metricsCount: data.metrics.length })) .sort((a, b) => b.duration - a.duration); phases.forEach(phase => { const percentage = (phase.duration / phases.reduce((sum, p) => sum + p.duration, 0)) * 100; const bar = 'ā–ˆ'.repeat(Math.floor(percentage / 2)); console.log(` ${phase.name.padEnd(30)} ${bar} ${(phase.duration / 1000).toFixed(3)}s (${percentage.toFixed(1)}%)`); if (phase.metricsCount > 0) { console.log(chalk.dim(` └─ ${phase.metricsCount} detailed metrics recorded`)); } }); } /** * Generate memory analysis */ generateMemoryAnalysis() { if (this.metrics.memorySnapshots.length === 0) return; console.log(chalk.bold('\nšŸ’¾ MEMORY ANALYSIS:')); console.log(chalk.gray('─'.repeat(60))); const firstSnapshot = this.metrics.memorySnapshots[0]; const lastSnapshot = this.metrics.memorySnapshots[this.metrics.memorySnapshots.length - 1]; // Heap usage const heapDelta = lastSnapshot.heapUsed - firstSnapshot.heapUsed; const heapColor = heapDelta > 0 ? chalk.yellow : chalk.green; console.log(' Heap Usage:'); console.log(` Initial: ${this.formatBytes(firstSnapshot.heapUsed)}`); console.log(` Final: ${this.formatBytes(lastSnapshot.heapUsed)}`); console.log(heapColor(` Change: ${heapDelta > 0 ? '+' : ''}${this.formatBytes(heapDelta)}`)); // Peak memory const peakHeap = Math.max(...this.metrics.memorySnapshots.map(s => s.heapUsed)); const peakRSS = Math.max(...this.metrics.memorySnapshots.map(s => s.rss)); console.log(' Peak Usage:'); console.log(` Heap: ${this.formatBytes(peakHeap)}`); console.log(` RSS: ${this.formatBytes(peakRSS)}`); // Memory trend this.generateMemoryTrend(); } /** * Generate memory trend visualization */ generateMemoryTrend() { if (this.metrics.memorySnapshots.length < 2) return; console.log(' Memory Trend:'); const samples = 20; const step = Math.max(1, Math.floor(this.metrics.memorySnapshots.length / samples)); const maxHeap = Math.max(...this.metrics.memorySnapshots.map(s => s.heapUsed)); for (let i = 0; i < this.metrics.memorySnapshots.length; i += step) { const snapshot = this.metrics.memorySnapshots[i]; const barLength = Math.floor((snapshot.heapUsed / maxHeap) * 30); const bar = 'ā–„'.repeat(barLength); console.log(chalk.dim(` ${bar} ${this.formatBytes(snapshot.heapUsed)}`)); } } /** * Generate API analysis */ generateApiAnalysis() { if (this.metrics.apiCalls.length === 0) return; console.log(chalk.bold('\n🌐 API PERFORMANCE:')); console.log(chalk.gray('─'.repeat(60))); // Group by endpoint const endpointStats = new Map(); this.metrics.apiCalls.forEach(call => { const key = `${call.method} ${call.endpoint}`; if (!endpointStats.has(key)) { endpointStats.set(key, this.createEmptyAggregation()); } this.updateAggregation(endpointStats.get(key), call.duration); }); // Display stats console.log(' Endpoint Statistics:'); endpointStats.forEach((stats, endpoint) => { console.log(` ${endpoint}:`); console.log(` Calls: ${stats.count}`); console.log(` Avg: ${stats.avg.toFixed(0)}ms`); console.log(` Min/Max: ${stats.min.toFixed(0)}ms / ${stats.max.toFixed(0)}ms`); console.log(` P95: ${stats.p95.toFixed(0)}ms`); }); // Total API metrics const totalApiTime = this.metrics.apiCalls.reduce((sum, call) => sum + call.duration, 0); const totalDataSize = this.metrics.apiCalls .filter(call => call.size) .reduce((sum, call) => sum + (call.size || 0), 0); console.log(' Overall API Metrics:'); console.log(` Total Calls: ${this.metrics.apiCalls.length}`); console.log(` Total Time: ${(totalApiTime / 1000).toFixed(2)}s`); console.log(` Total Data: ${this.formatBytes(totalDataSize)}`); // Error rate const errorCalls = this.metrics.apiCalls.filter(call => call.statusCode >= 400); if (errorCalls.length > 0) { console.log(chalk.red(` Error Rate: ${((errorCalls.length / this.metrics.apiCalls.length) * 100).toFixed(1)}%`)); } } /** * Generate database analysis */ generateDatabaseAnalysis() { if (this.metrics.databaseQueries.length === 0) return; console.log(chalk.bold('\nšŸ—„ļø DATABASE PERFORMANCE:')); console.log(chalk.gray('─'.repeat(60))); // Query statistics const totalQueries = this.metrics.databaseQueries.length; const totalQueryTime = this.metrics.databaseQueries.reduce((sum, q) => sum + q.duration, 0); const totalRows = this.metrics.databaseQueries.reduce((sum, q) => sum + q.rowCount, 0); console.log(' Query Statistics:'); console.log(` Total Queries: ${totalQueries}`); console.log(` Total Time: ${(totalQueryTime / 1000).toFixed(3)}s`); console.log(` Total Rows: ${totalRows.toLocaleString()}`); console.log(` Avg Query Time: ${(totalQueryTime / totalQueries).toFixed(1)}ms`); // Slowest queries const slowestQueries = this.metrics.databaseQueries .sort((a, b) => b.duration - a.duration) .slice(0, 5); if (slowestQueries.length > 0) { console.log(' Slowest Queries:'); slowestQueries.forEach((query, index) => { console.log(` ${index + 1}. ${query.query}`); console.log(chalk.dim(` Duration: ${query.duration.toFixed(0)}ms, Rows: ${query.rowCount}`)); }); } } /** * Generate error analysis */ generateErrorAnalysis() { if (this.metrics.errors.length === 0) { console.log(chalk.bold.green('\nāœ… ERROR ANALYSIS: No errors recorded')); return; } console.log(chalk.bold.red('\nāŒ ERROR ANALYSIS:')); console.log(chalk.gray('─'.repeat(60))); // Group by phase const errorsByPhase = new Map(); this.metrics.errors.forEach(error => { errorsByPhase.set(error.phase, (errorsByPhase.get(error.phase) || 0) + 1); }); console.log(' Errors by Phase:'); errorsByPhase.forEach((count, phase) => { console.log(chalk.red(` ${phase}: ${count} error${count > 1 ? 's' : ''}`)); }); // Recent errors console.log(' Recent Errors:'); this.metrics.errors.slice(-3).forEach(error => { console.log(chalk.red(` [${error.phase}] ${error.error}`)); if (error.context) { console.log(chalk.dim(` Context: ${JSON.stringify(error.context)}`)); } }); } /** * Generate summary statistics */ generateSummaryStatistics() { console.log(chalk.bold('\nšŸ“ˆ SUMMARY STATISTICS:')); console.log(chalk.gray('─'.repeat(60))); const totalDuration = (this.metrics.endTime || performance.now()) - this.metrics.startTime; const apiTime = this.metrics.apiCalls.reduce((sum, call) => sum + call.duration, 0); const dbTime = this.metrics.databaseQueries.reduce((sum, q) => sum + q.duration, 0); // Time distribution console.log(' Time Distribution:'); console.log(` API Calls: ${((apiTime / totalDuration) * 100).toFixed(1)}%`); console.log(` Database: ${((dbTime / totalDuration) * 100).toFixed(1)}%`); console.log(` Other: ${(((totalDuration - apiTime - dbTime) / totalDuration) * 100).toFixed(1)}%`); // Efficiency metrics const throughput = this.metrics.databaseQueries.reduce((sum, q) => sum + q.rowCount, 0) / (totalDuration / 1000); console.log(' Efficiency Metrics:'); console.log(` Throughput: ${throughput.toFixed(1)} records/second`); console.log(` API Efficiency: ${(this.metrics.apiCalls.length / (apiTime / 1000)).toFixed(1)} calls/second`); // Resource utilization if (this.metrics.memorySnapshots.length > 0) { const avgHeap = this.metrics.memorySnapshots.reduce((sum, s) => sum + s.heapUsed, 0) / this.metrics.memorySnapshots.length; console.log(' Resource Utilization:'); console.log(` Avg Heap: ${this.formatBytes(avgHeap)}`); console.log(` Memory Samples: ${this.metrics.memorySnapshots.length}`); } } /** * Helper: Format bytes */ formatBytes(bytes) { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size > 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(2)} ${units[unitIndex]}`; } /** * Helper: Create empty aggregation */ createEmptyAggregation() { return { min: Infinity, max: -Infinity, avg: 0, median: 0, p95: 0, p99: 0, count: 0 }; } /** * Helper: Update aggregation */ updateAggregation(agg, value) { agg.min = Math.min(agg.min, value); agg.max = Math.max(agg.max, value); agg.avg = (agg.avg * agg.count + value) / (agg.count + 1); agg.count++; // Note: Proper percentile calculation would require storing all values agg.median = agg.avg; // Simplified agg.p95 = agg.max * 0.95; // Simplified agg.p99 = agg.max * 0.99; // Simplified } /** * Export metrics data */ exportMetrics() { return this.metrics; } } //# sourceMappingURL=VerboseMetricsReporter.js.map