UNPKG

ultimate-mcp-server

Version:

The definitive all-in-one Model Context Protocol server for AI-assisted coding across 30+ platforms

397 lines 14.3 kB
/** * Performance Monitoring System * * Tracks metrics, performance, and provides insights * for Ultimate MCP server operations */ import { EventEmitter } from 'events'; export class PerformanceMonitor extends EventEmitter { toolMetrics = new Map(); modelMetrics = new Map(); systemMetrics = { startTime: Date.now(), totalRequests: 0, activeRequests: 0, peakActiveRequests: 0, memoryUsage: [], cpuUsage: [] }; metricsInterval; constructor() { super(); this.startSystemMetricsCollection(); } /** * Start tracking tool execution */ activeToolExecutions = new Map(); startToolExecution(toolName) { this.activeToolExecutions.set(toolName, { startTime: Date.now() }); this.systemMetrics.activeRequests++; this.systemMetrics.totalRequests++; if (this.systemMetrics.activeRequests > this.systemMetrics.peakActiveRequests) { this.systemMetrics.peakActiveRequests = this.systemMetrics.activeRequests; } } /** * End tracking tool execution */ endToolExecution(toolName, success, error) { const execution = this.activeToolExecutions.get(toolName); if (!execution) { throw new Error(`No active execution found for tool: ${toolName}`); } const duration = Date.now() - execution.startTime; this.activeToolExecutions.delete(toolName); this.systemMetrics.activeRequests--; this.trackToolExecution(toolName, duration, success, error); } /** * Record model usage */ recordModelUsage(model, tokens, latency, success, inputTokens, outputTokens, cost) { // For backward compatibility, use tokens as total if input/output not provided const actualInputTokens = inputTokens ?? Math.floor(tokens * 0.3); const actualOutputTokens = outputTokens ?? Math.floor(tokens * 0.7); const actualCost = cost ?? this.estimateCost(model, actualInputTokens, actualOutputTokens); this.trackModelUsage(model, actualInputTokens, actualOutputTokens, actualCost, latency, success); } /** * Estimate cost based on model and tokens */ estimateCost(model, inputTokens, outputTokens) { // Simple cost estimation - in real implementation would use actual pricing const rates = { 'gpt-4': { input: 0.03, output: 0.06 }, 'gpt-3.5-turbo': { input: 0.001, output: 0.002 }, 'claude-3-opus': { input: 0.015, output: 0.075 }, 'claude-3-sonnet': { input: 0.003, output: 0.015 } }; const baseRate = { input: 0.001, output: 0.002 }; const modelRates = Object.entries(rates).find(([key]) => model.includes(key))?.[1] || baseRate; return (inputTokens * modelRates.input + outputTokens * modelRates.output) / 1000; } /** * Track tool execution */ trackToolExecution(toolName, duration, success, error) { if (!this.toolMetrics.has(toolName)) { this.toolMetrics.set(toolName, []); } const metrics = this.toolMetrics.get(toolName); metrics.push({ timestamp: Date.now(), duration, success, error }); // Keep only last 1000 metrics per tool if (metrics.length > 1000) { metrics.shift(); } // Emit event for real-time monitoring this.emit('tool:executed', { tool: toolName, duration, success, error }); } /** * Track model usage */ trackModelUsage(model, inputTokens, outputTokens, cost, latency, success) { if (!this.modelMetrics.has(model)) { this.modelMetrics.set(model, { requests: 0, tokens: { input: 0, output: 0, total: 0 }, cost: 0, averageLatency: 0, errors: 0 }); } const metrics = this.modelMetrics.get(model); metrics.requests++; metrics.tokens.input += inputTokens; metrics.tokens.output += outputTokens; metrics.tokens.total += inputTokens + outputTokens; metrics.cost += cost; // Update average latency metrics.averageLatency = (metrics.averageLatency * (metrics.requests - 1) + latency) / metrics.requests; if (!success) { metrics.errors++; } // Emit event this.emit('model:used', { model, tokens: { input: inputTokens, output: outputTokens }, cost, latency }); } /** * Track request lifecycle */ startRequest() { const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2)}`; this.systemMetrics.totalRequests++; this.systemMetrics.activeRequests++; if (this.systemMetrics.activeRequests > this.systemMetrics.peakActiveRequests) { this.systemMetrics.peakActiveRequests = this.systemMetrics.activeRequests; } return requestId; } endRequest(requestId) { this.systemMetrics.activeRequests--; } /** * Get tool metrics */ getToolMetrics(toolName) { if (toolName) { const metrics = this.toolMetrics.get(toolName); if (!metrics || metrics.length === 0) { // Return default metrics instead of null for consistency return { invocations: 0, totalDuration: 0, averageDuration: 0, minDuration: 0, maxDuration: 0, successRate: 0, lastUsed: 0, errors: 0, calls: 0 }; } return this.calculateToolMetrics(toolName, metrics); } // Return all tool metrics const allMetrics = {}; for (const [name, metrics] of this.toolMetrics.entries()) { if (metrics.length > 0) { allMetrics[name] = this.calculateToolMetrics(name, metrics); } } return allMetrics; } /** * Calculate metrics for a tool */ calculateToolMetrics(toolName, metrics) { const durations = metrics.map(m => m.duration); const successful = metrics.filter(m => m.success).length; return { invocations: metrics.length, totalDuration: durations.reduce((a, b) => a + b, 0), averageDuration: durations.reduce((a, b) => a + b, 0) / durations.length, minDuration: Math.min(...durations), maxDuration: Math.max(...durations), successRate: (successful / metrics.length) * 100, lastUsed: metrics[metrics.length - 1].timestamp, errors: metrics.length - successful, calls: metrics.length // Added for backward compatibility }; } /** * Get model metrics */ getModelMetrics(model) { if (model) { return this.modelMetrics.get(model) || null; } return Object.fromEntries(this.modelMetrics.entries()); } /** * Get performance summary */ getPerformanceSummary() { const summary = { uptime: Date.now() - this.systemMetrics.startTime, totalRequests: this.systemMetrics.totalRequests, activeRequests: this.systemMetrics.activeRequests, peakActiveRequests: this.systemMetrics.peakActiveRequests, tools: {}, models: {}, memoryUsage: this.systemMetrics.memoryUsage[this.systemMetrics.memoryUsage.length - 1] || null }; // Aggregate tool metrics const toolMetrics = this.getToolMetrics(); summary.tools = toolMetrics; // Aggregate model metrics const modelMetrics = this.getModelMetrics(); summary.models = modelMetrics; return summary; } /** * Get system metrics */ getSystemMetrics() { const uptime = Date.now() - this.systemMetrics.startTime; const memoryUsage = process.memoryUsage(); return { uptime, uptimeHuman: this.formatUptime(uptime), totalRequests: this.systemMetrics.totalRequests, activeRequests: this.systemMetrics.activeRequests, peakActiveRequests: this.systemMetrics.peakActiveRequests, requestsPerMinute: (this.systemMetrics.totalRequests / (uptime / 60000)).toFixed(2), memory: { current: { heapUsed: memoryUsage.heapUsed, heapTotal: memoryUsage.heapTotal, external: memoryUsage.external, rss: memoryUsage.rss }, history: this.systemMetrics.memoryUsage.slice(-20) // Last 20 samples }, cpu: { history: this.systemMetrics.cpuUsage.slice(-20) // Last 20 samples } }; } /** * Get cost breakdown */ getCostBreakdown() { const breakdown = {}; let totalCost = 0; // Calculate total cost for (const metrics of this.modelMetrics.values()) { totalCost += metrics.cost; } // Calculate breakdown for (const [model, metrics] of this.modelMetrics.entries()) { breakdown[model] = { cost: metrics.cost, percentage: totalCost > 0 ? (metrics.cost / totalCost) * 100 : 0 }; } return { total: totalCost, breakdown, byProvider: this.groupCostByProvider() }; } /** * Group costs by provider */ groupCostByProvider() { const providers = {}; for (const [model, metrics] of this.modelMetrics.entries()) { const provider = model.split('/')[0] || 'unknown'; providers[provider] = (providers[provider] || 0) + metrics.cost; } return providers; } /** * Get performance insights */ getInsights() { const insights = []; // Analyze tool performance const toolMetrics = this.getToolMetrics(); const slowTools = Object.entries(toolMetrics) .filter(([_, metrics]) => metrics.averageDuration > 5000) .sort((a, b) => b[1].averageDuration - a[1].averageDuration); if (slowTools.length > 0) { insights.push(`Slow tools detected: ${slowTools.map(([name]) => name).join(', ')}`); } // Analyze error rates const highErrorTools = Object.entries(toolMetrics) .filter(([_, metrics]) => metrics.successRate < 90) .sort((a, b) => a[1].successRate - b[1].successRate); if (highErrorTools.length > 0) { insights.push(`High error rate tools: ${highErrorTools.map(([name, m]) => `${name} (${m.successRate.toFixed(1)}%)`).join(', ')}`); } // Analyze costs const costBreakdown = this.getCostBreakdown(); if (costBreakdown.total > 10) { insights.push(`High API costs: $${costBreakdown.total.toFixed(2)} total`); } // Memory usage const currentMemory = process.memoryUsage(); const heapPercentage = (currentMemory.heapUsed / currentMemory.heapTotal) * 100; if (heapPercentage > 80) { insights.push(`High memory usage: ${heapPercentage.toFixed(1)}% of heap`); } return insights; } /** * Export metrics for analysis */ exportMetrics() { return { timestamp: Date.now(), tools: Object.fromEntries(Array.from(this.toolMetrics.entries()).map(([name, metrics]) => [ name, metrics.slice(-100) // Last 100 executions ])), models: Object.fromEntries(this.modelMetrics.entries()), system: this.getSystemMetrics() }; } /** * Start collecting system metrics */ startSystemMetricsCollection() { // Collect every 30 seconds this.metricsInterval = setInterval(() => { const memoryUsage = process.memoryUsage(); this.systemMetrics.memoryUsage.push({ timestamp: Date.now(), used: memoryUsage.heapUsed, total: memoryUsage.heapTotal }); // Keep only last hour of data const oneHourAgo = Date.now() - 3600000; this.systemMetrics.memoryUsage = this.systemMetrics.memoryUsage .filter(m => m.timestamp > oneHourAgo); // CPU usage (simplified - in production use proper CPU monitoring) const cpuUsage = process.cpuUsage(); this.systemMetrics.cpuUsage.push({ timestamp: Date.now(), usage: (cpuUsage.user + cpuUsage.system) / 1000000 // Convert to seconds }); this.systemMetrics.cpuUsage = this.systemMetrics.cpuUsage .filter(m => m.timestamp > oneHourAgo); // Emit metrics event this.emit('metrics:collected', { memory: memoryUsage, cpu: cpuUsage }); }, 30000); } /** * Format uptime */ formatUptime(ms) { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) { return `${days}d ${hours % 24}h ${minutes % 60}m`; } else if (hours > 0) { return `${hours}h ${minutes % 60}m ${seconds % 60}s`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } } /** * Cleanup */ destroy() { if (this.metricsInterval) { clearInterval(this.metricsInterval); } this.removeAllListeners(); } } //# sourceMappingURL=performance-monitor.js.map