UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

272 lines (271 loc) 10.3 kB
"use strict"; /** * @fileoverview Performance Monitor - Clean Architecture Performance Tracking * @version 1.0.0 * @since 2025-07-30 * @lastUpdated 2025-07-30 * @module PerformanceMonitor * @description Comprehensive performance monitoring for Clean Architecture components * @contributors Claude Code Agent * @dependencies none * @requirements REQ-PERF-001 (Performance Monitoring) * @testCoverage Unit tests for metrics collection and analysis */ Object.defineProperty(exports, "__esModule", { value: true }); exports.performanceMonitor = exports.PerformanceTracker = exports.PerformanceMonitor = void 0; exports.trackPerformance = trackPerformance; class PerformanceMonitor { constructor() { this.metrics = []; this.maxMetricsHistory = 1000; this.thresholds = { maxDuration: 5000, // 5 seconds maxMemoryUsage: 100 * 1024 * 1024, // 100MB maxDatabaseQueries: 10, }; } /** * Start monitoring a performance operation */ startOperation(operationType) { return new PerformanceTracker(operationType); } /** * Record performance metrics */ recordMetrics(metrics) { this.metrics.push(metrics); // Keep only recent metrics if (this.metrics.length > this.maxMetricsHistory) { this.metrics = this.metrics.slice(-this.maxMetricsHistory); } // Log performance warnings this.checkThresholds(metrics); // Emit performance event for monitoring systems this.emitPerformanceEvent(metrics); } /** * Get performance statistics */ getStatistics(timeWindow) { const cutoffTime = timeWindow ? Date.now() - timeWindow : 0; const relevantMetrics = this.metrics.filter(m => new Date(m.timestamp).getTime() > cutoffTime); if (relevantMetrics.length === 0) { return this.getEmptyStatistics(); } const durations = relevantMetrics.map(m => m.duration); const memoryUsages = relevantMetrics.map(m => m.memoryUsage.heapUsed); const successRate = relevantMetrics.filter(m => m.success).length / relevantMetrics.length; const cacheHitRate = this.calculateCacheHitRate(relevantMetrics); return { totalOperations: relevantMetrics.length, successRate, cacheHitRate, averageDuration: this.calculateAverage(durations), medianDuration: this.calculateMedian(durations), p95Duration: this.calculatePercentile(durations, 95), p99Duration: this.calculatePercentile(durations, 99), maxDuration: Math.max(...durations), minDuration: Math.min(...durations), averageMemoryUsage: this.calculateAverage(memoryUsages), maxMemoryUsage: Math.max(...memoryUsages), operationBreakdown: this.getOperationBreakdown(relevantMetrics), performanceIssues: this.identifyPerformanceIssues(relevantMetrics), timeWindow: timeWindow ? String(timeWindow) : 'all-time', generatedAt: new Date().toISOString(), }; } /** * Get slowest operations */ getSlowestOperations(limit = 10) { return [...this.metrics].sort((a, b) => b.duration - a.duration).slice(0, limit); } /** * Get memory-intensive operations */ getMemoryIntensiveOperations(limit = 10) { return [...this.metrics] .sort((a, b) => b.memoryUsage.heapUsed - a.memoryUsage.heapUsed) .slice(0, limit); } /** * Clear metrics history */ clearMetrics() { this.metrics = []; console.log('📊 Performance metrics cleared'); } /** * Export metrics for external analysis */ exportMetrics() { return { metrics: [...this.metrics], statistics: this.getStatistics(), exportedAt: new Date().toISOString(), }; } checkThresholds(metrics) { const issues = []; if (metrics.duration > this.thresholds.maxDuration) { issues.push(`Duration exceeded threshold: ${metrics.duration}ms > ${this.thresholds.maxDuration}ms`); } if (metrics.memoryUsage.heapUsed > this.thresholds.maxMemoryUsage) { issues.push(`Memory usage exceeded threshold: ${metrics.memoryUsage.heapUsed} > ${this.thresholds.maxMemoryUsage}`); } if (metrics.databaseQueries && metrics.databaseQueries > this.thresholds.maxDatabaseQueries) { issues.push(`Database queries exceeded threshold: ${metrics.databaseQueries} > ${this.thresholds.maxDatabaseQueries}`); } if (issues.length > 0) { console.warn(`⚠️ Performance threshold violations in ${metrics.operationType}:`, issues); } } emitPerformanceEvent(metrics) { // In a real implementation, this would emit to monitoring systems // like Prometheus, DataDog, or custom metrics collectors console.log(`📊 Performance: ${metrics.operationType} completed in ${metrics.duration}ms`); } calculateAverage(values) { return values.reduce((sum, val) => sum + val, 0) / values.length; } calculateMedian(values) { const sorted = [...values].sort((a, b) => a - b); const mid = Math.floor(sorted.length / 2); return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; } calculatePercentile(values, percentile) { const sorted = [...values].sort((a, b) => a - b); const index = Math.ceil((percentile / 100) * sorted.length) - 1; return sorted[index] || 0; } calculateCacheHitRate(metrics) { const cacheableOperations = metrics.filter(m => m.cacheHit !== undefined); if (cacheableOperations.length === 0) return 0; const cacheHits = cacheableOperations.filter(m => m.cacheHit).length; return cacheHits / cacheableOperations.length; } getOperationBreakdown(metrics) { const breakdown = {}; metrics.forEach(m => { breakdown[m.operationType] = (breakdown[m.operationType] || 0) + 1; }); return breakdown; } identifyPerformanceIssues(metrics) { const issues = []; // Check for consistently slow operations const slowOperations = metrics.filter(m => m.duration > this.thresholds.maxDuration); if (slowOperations.length > metrics.length * 0.1) { issues.push(`High percentage of slow operations: ${slowOperations.length}/${metrics.length}`); } // Check for memory leaks const recentMetrics = metrics.slice(-10); if (recentMetrics.length >= 5) { const memoryTrend = this.calculateMemoryTrend(recentMetrics); if (memoryTrend > 10 * 1024 * 1024) { // 10MB increase trend issues.push('Potential memory leak detected - increasing memory usage trend'); } } // Check for low cache hit rate const cacheHitRate = this.calculateCacheHitRate(metrics); if (cacheHitRate < 0.5 && cacheHitRate > 0) { issues.push(`Low cache hit rate: ${(cacheHitRate * 100).toFixed(1)}%`); } return issues; } calculateMemoryTrend(metrics) { if (metrics.length < 2) return 0; const first = metrics[0].memoryUsage.heapUsed; const last = metrics[metrics.length - 1].memoryUsage.heapUsed; return last - first; } getEmptyStatistics() { return { totalOperations: 0, successRate: 0, cacheHitRate: 0, averageDuration: 0, medianDuration: 0, p95Duration: 0, p99Duration: 0, maxDuration: 0, minDuration: 0, averageMemoryUsage: 0, maxMemoryUsage: 0, operationBreakdown: {}, performanceIssues: [], timeWindow: 'all-time', generatedAt: new Date().toISOString(), }; } } exports.PerformanceMonitor = PerformanceMonitor; class PerformanceTracker { constructor(operationType) { this.operationType = operationType; this.databaseQueries = 0; this.eventCount = 0; this.startTime = process.hrtime(); this.startMemory = process.memoryUsage(); } /** * Record a database query */ recordDatabaseQuery() { this.databaseQueries++; } /** * Record an event */ recordEvent() { this.eventCount++; } /** * Record cache hit/miss */ recordCacheHit(hit) { this.cacheHit = hit; } /** * Complete tracking and return metrics */ complete(success = true) { const [seconds, nanoseconds] = process.hrtime(this.startTime); const duration = seconds * 1000 + nanoseconds / 1000000; // Convert to milliseconds const currentMemory = process.memoryUsage(); return { duration: Math.round(duration * 100) / 100, // Round to 2 decimal places memoryUsage: currentMemory, timestamp: new Date().toISOString(), operationType: this.operationType, success, cacheHit: this.cacheHit, databaseQueries: this.databaseQueries, eventCount: this.eventCount, }; } } exports.PerformanceTracker = PerformanceTracker; // Global performance monitor instance exports.performanceMonitor = new PerformanceMonitor(); // Helper function for easy performance tracking function trackPerformance(operationType, operation) { return new Promise(async (resolve, reject) => { const tracker = exports.performanceMonitor.startOperation(operationType); try { const result = await operation(tracker); const metrics = tracker.complete(true); exports.performanceMonitor.recordMetrics(metrics); resolve(result); } catch (error) { const metrics = tracker.complete(false); exports.performanceMonitor.recordMetrics(metrics); reject(error); } }); }