Skip to main content
Glama
performance-monitor.ts9.61 kB
import { performance, PerformanceObserver } from 'perf_hooks'; import { EventEmitter } from 'events'; export interface PerformanceMetrics { timestamp: number; memoryUsage: NodeJS.MemoryUsage; cpuUsage: NodeJS.CpuUsage; requestCount: number; averageResponseTime: number; errorRate: number; cacheHitRate: number; circuitBreakerState: string; } export interface RequestMetrics { method: string; url: string; duration: number; status: number; cacheHit: boolean; timestamp: number; } /** * Performance monitoring and optimization utilities */ export class PerformanceMonitor extends EventEmitter { private metrics: PerformanceMetrics; private requestHistory: RequestMetrics[] = []; private readonly maxHistorySize = 1000; private lastCpuUsage: NodeJS.CpuUsage; private observer?: PerformanceObserver; // Performance thresholds private readonly thresholds = { maxResponseTime: 5000, // 5 seconds maxMemoryUsage: 100 * 1024 * 1024, // 100MB maxErrorRate: 0.05, // 5% minCacheHitRate: 0.7, // 70% }; constructor() { super(); this.lastCpuUsage = process.cpuUsage(); this.metrics = this.initializeMetrics(); this.setupPerformanceObserver(); this.startMonitoring(); } private initializeMetrics(): PerformanceMetrics { return { timestamp: Date.now(), memoryUsage: process.memoryUsage(), cpuUsage: process.cpuUsage(), requestCount: 0, averageResponseTime: 0, errorRate: 0, cacheHitRate: 0, circuitBreakerState: 'CLOSED', }; } private setupPerformanceObserver(): void { this.observer = new PerformanceObserver((list) => { const entries = list.getEntries(); for (const entry of entries) { if (entry.entryType === 'measure') { this.recordPerformanceMeasure(entry); } } }); try { this.observer.observe({ entryTypes: ['measure'] }); } catch (error) { console.warn('Performance observer setup failed:', error); } } private recordPerformanceMeasure(entry: any): void { this.emit('performance-entry', { name: entry.name, duration: entry.duration, startTime: entry.startTime, }); } /** * Start periodic monitoring */ private startMonitoring(): void { // Update metrics every 30 seconds setInterval(() => { this.updateMetrics(); this.checkThresholds(); }, 30000); // Clean old request history every 5 minutes setInterval(() => { this.cleanOldRequests(); }, 300000); } /** * Record HTTP request metrics */ recordRequest(request: RequestMetrics): void { this.requestHistory.push(request); // Maintain history size limit if (this.requestHistory.length > this.maxHistorySize) { this.requestHistory.shift(); } this.emit('request-recorded', request); } /** * Start timing a specific operation */ startTimer(name: string): void { performance.mark(`${name}-start`); } /** * End timing and get duration */ endTimer(name: string): number { const endMark = `${name}-end`; const measureName = `${name}-duration`; performance.mark(endMark); performance.measure(measureName, `${name}-start`, endMark); const measure = performance.getEntriesByName(measureName)[0]; return measure ? measure.duration : 0; } /** * Get current performance metrics */ getCurrentMetrics(): PerformanceMetrics { this.updateMetrics(); return { ...this.metrics }; } /** * Update internal metrics */ private updateMetrics(): void { const now = Date.now(); const recentRequests = this.getRecentRequests(300000); // Last 5 minutes this.metrics = { timestamp: now, memoryUsage: process.memoryUsage(), cpuUsage: process.cpuUsage(this.lastCpuUsage), requestCount: this.requestHistory.length, averageResponseTime: this.calculateAverageResponseTime(recentRequests), errorRate: this.calculateErrorRate(recentRequests), cacheHitRate: this.calculateCacheHitRate(recentRequests), circuitBreakerState: 'CLOSED', // This would be updated from circuit breaker }; this.lastCpuUsage = process.cpuUsage(); } private getRecentRequests(maxAge: number): RequestMetrics[] { const cutoff = Date.now() - maxAge; return this.requestHistory.filter(req => req.timestamp > cutoff); } private calculateAverageResponseTime(requests: RequestMetrics[]): number { if (requests.length === 0) return 0; const total = requests.reduce((sum, req) => sum + req.duration, 0); return total / requests.length; } private calculateErrorRate(requests: RequestMetrics[]): number { if (requests.length === 0) return 0; const errors = requests.filter(req => req.status >= 400).length; return errors / requests.length; } private calculateCacheHitRate(requests: RequestMetrics[]): number { if (requests.length === 0) return 0; const hits = requests.filter(req => req.cacheHit).length; return hits / requests.length; } private checkThresholds(): void { const alerts: string[] = []; if (this.metrics.averageResponseTime > this.thresholds.maxResponseTime) { alerts.push(`High response time: ${this.metrics.averageResponseTime.toFixed(2)}ms`); } if (this.metrics.memoryUsage.heapUsed > this.thresholds.maxMemoryUsage) { alerts.push(`High memory usage: ${(this.metrics.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)}MB`); } if (this.metrics.errorRate > this.thresholds.maxErrorRate) { alerts.push(`High error rate: ${(this.metrics.errorRate * 100).toFixed(2)}%`); } if (this.metrics.cacheHitRate < this.thresholds.minCacheHitRate) { alerts.push(`Low cache hit rate: ${(this.metrics.cacheHitRate * 100).toFixed(2)}%`); } if (alerts.length > 0) { this.emit('performance-alert', alerts); } } private cleanOldRequests(): void { const cutoff = Date.now() - (24 * 60 * 60 * 1000); // 24 hours this.requestHistory = this.requestHistory.filter(req => req.timestamp > cutoff); } /** * Get performance summary report */ getPerformanceReport(): { current: PerformanceMetrics; summary: { totalRequests: number; avgResponseTime: number; errorRate: number; cacheHitRate: number; memoryUsageMB: number; cpuUsagePercent: number; }; recommendations: string[]; } { const current = this.getCurrentMetrics(); const recommendations: string[] = []; // Generate optimization recommendations if (current.averageResponseTime > 1000) { recommendations.push('Consider enabling response caching for frequently accessed data'); } if (current.cacheHitRate < 0.5) { recommendations.push('Increase cache TTL or implement more aggressive caching strategies'); } if (current.memoryUsage.heapUsed > 50 * 1024 * 1024) { recommendations.push('Monitor for memory leaks and consider implementing object pooling'); } if (current.errorRate > 0.02) { recommendations.push('Implement circuit breaker patterns and improve error handling'); } return { current, summary: { totalRequests: this.requestHistory.length, avgResponseTime: current.averageResponseTime, errorRate: current.errorRate, cacheHitRate: current.cacheHitRate, memoryUsageMB: current.memoryUsage.heapUsed / 1024 / 1024, cpuUsagePercent: (current.cpuUsage.user + current.cpuUsage.system) / 1000000, // Convert microseconds to percentage }, recommendations, }; } /** * Get detailed memory analysis */ getMemoryAnalysis(): { usage: NodeJS.MemoryUsage; analysis: { heapUtilization: number; externalMemoryRatio: number; recommendations: string[]; }; } { const usage = process.memoryUsage(); const heapUtilization = usage.heapUsed / usage.heapTotal; const externalMemoryRatio = usage.external / usage.heapUsed; const recommendations: string[] = []; if (heapUtilization > 0.8) { recommendations.push('Heap utilization is high - consider garbage collection tuning'); } if (externalMemoryRatio > 0.5) { recommendations.push('High external memory usage - review buffer and stream usage'); } if (usage.arrayBuffers > 10 * 1024 * 1024) { recommendations.push('Large ArrayBuffer usage detected - optimize binary data handling'); } return { usage, analysis: { heapUtilization, externalMemoryRatio, recommendations, }, }; } /** * Cleanup resources */ cleanup(): void { if (this.observer) { this.observer.disconnect(); } this.removeAllListeners(); } } /** * Singleton performance monitor instance */ export const performanceMonitor = new PerformanceMonitor(); /** * Decorator for measuring function execution time */ export function measurePerformance(target: any, propertyName: string, descriptor: PropertyDescriptor) { const method = descriptor.value; descriptor.value = function (...args: any[]) { const timerName = `${target.constructor.name}.${propertyName}`; performanceMonitor.startTimer(timerName); const result = method.apply(this, args); if (result instanceof Promise) { return result.finally(() => { performanceMonitor.endTimer(timerName); }); } else { performanceMonitor.endTimer(timerName); return result; } }; return descriptor; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/quanticsoul4772/grafana-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server