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
JavaScript
;
/**
* @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);
}
});
}