UNPKG

@iota-big3/sdk-gateway

Version:

Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching

345 lines 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PerformanceMonitor = void 0; const events_1 = require("events"); const minimal_analytics_1 = require("../utils/minimal-analytics"); class PerformanceMonitor extends events_1.EventEmitter { constructor(thresholds) { super(); this.requests = new Map(); // Request ID -> start time this.latencies = []; this.windowSize = 60000; // 1 minute window for calculations this.lastReset = Date.now(); this.anomalies = []; this.metrics = new minimal_analytics_1.SimpleTimeSeriesStore(); this.thresholds = { latency: { p50: thresholds?.latency?.p50 || 50, p95: thresholds?.latency?.p95 || 150, p99: thresholds?.latency?.p99 || 300 }, errorRate: thresholds?.errorRate || 0.01, // 1% throughput: { min: thresholds?.throughput?.min || 100, max: thresholds?.throughput?.max || 10000 }, memory: { max: thresholds?.memory?.max || 512, // MB growthRate: thresholds?.memory?.growthRate || 10 // MB/hour } }; // Start periodic metric collection this.startMetricCollection(); } startRequest(request) { this.requests.set(request.id, Date.now()); this.metrics.record('requests.active', this.requests.size); } endRequest(request, response) { const startTime = this.requests.get(request.id); if (!startTime) return; const latency = Date.now() - startTime; this.requests.delete(request.id); // Record metrics this.collectMetrics(request, response, latency); // Check for anomalies this.checkAnomalies(); } collectMetrics(request, response, latency) { // Request metrics this.metrics.record('requests.total', 1); this.metrics.record('requests.active', this.requests.size); this.latencies.push(latency); // Latency metrics this.metrics.record('latency.current', latency, { path: request.path, method: request.method }); // Error tracking if (response.statusCode >= 400) { this.metrics.record('errors.total', 1, { status: String(response.statusCode), path: request.path }); } // Throughput (approximate based on response size) const responseSize = JSON.stringify(response.body).length; this.metrics.record('throughput.bytes', responseSize); // Resource metrics this.recordResourceMetrics(); // SLA compliance const slaCompliant = this.checkSLACompliance(request.path, latency, response.statusCode); this.metrics.record('sla.compliance', slaCompliant ? 1 : 0, { path: request.path }); } recordResourceMetrics() { const memUsage = process.memoryUsage(); const cpuUsage = process.cpuUsage(); // Memory metrics in MB this.metrics.record('memory.rss', memUsage.rss / 1024 / 1024); this.metrics.record('memory.heapUsed', memUsage.heapUsed / 1024 / 1024); this.metrics.record('memory.heapTotal', memUsage.heapTotal / 1024 / 1024); this.metrics.record('memory.external', memUsage.external / 1024 / 1024); // CPU metrics this.metrics.record('cpu.user', cpuUsage.user / 1000000); // Convert to seconds this.metrics.record('cpu.system', cpuUsage.system / 1000000); } checkSLACompliance(path, latency, statusCode) { // Basic SLA rules if (latency > this.thresholds.latency.p95) return false; if (statusCode >= 500) return false; // Path-specific SLAs could be added here if (path.startsWith('/api/critical/') && latency > 50) return false; return true; } detectAnomalies() { this.anomalies = [ ...this.detectLatencySpikes(), ...this.detectMemoryLeaks(), ...this.detectErrorRateIncrease(), ...this.detectThroughputDegradation() ]; // Emit critical anomalies this.anomalies .filter(a => a.severity === 'critical') .forEach(anomaly => this.emit('anomaly:critical', anomaly)); return this.anomalies; } detectLatencySpikes() { const anomalies = []; const recentLatencies = this.latencies.slice(-100); // Last 100 requests if (recentLatencies.length === 0) return anomalies; const p95 = this.calculatePercentile(recentLatencies, 95); const p99 = this.calculatePercentile(recentLatencies, 99); if (p95 > this.thresholds.latency.p95) { anomalies.push({ type: 'latency', severity: p95 > this.thresholds.latency.p95 * 2 ? 'high' : 'medium', value: p95, threshold: this.thresholds.latency.p95, timestamp: Date.now(), message: `P95 latency ${p95}ms exceeds threshold ${this.thresholds.latency.p95}ms` }); } if (p99 > this.thresholds.latency.p99) { anomalies.push({ type: 'latency', severity: 'critical', value: p99, threshold: this.thresholds.latency.p99, timestamp: Date.now(), message: `P99 latency ${p99}ms exceeds threshold ${this.thresholds.latency.p99}ms` }); } return anomalies; } detectMemoryLeaks() { const anomalies = []; const memoryData = this.metrics.query({ metric: 'memory.heapUsed', startTime: Date.now() - 3600000 // Last hour }); if (memoryData.length < 2) return anomalies; // Calculate growth rate const firstValue = memoryData[0]?.value || 0; const lastValue = memoryData[memoryData.length - 1]?.value || 0; const growthMB = lastValue - firstValue; if (growthMB > this.thresholds.memory.growthRate) { anomalies.push({ type: 'memory', severity: growthMB > this.thresholds.memory.growthRate * 2 ? 'critical' : 'high', value: growthMB, threshold: this.thresholds.memory.growthRate, timestamp: Date.now(), message: `Memory grew by ${growthMB.toFixed(2)}MB in the last hour` }); } // Check absolute memory usage if (lastValue > this.thresholds.memory.max) { anomalies.push({ type: 'memory', severity: 'critical', value: lastValue, threshold: this.thresholds.memory.max, timestamp: Date.now(), message: `Memory usage ${lastValue.toFixed(2)}MB exceeds maximum ${this.thresholds.memory.max}MB` }); } return anomalies; } detectErrorRateIncrease() { const anomalies = []; const recentRequests = this.metrics.query({ metric: 'requests.total', startTime: Date.now() - 60000 // Last minute }); const recentErrors = this.metrics.query({ metric: 'errors.total', startTime: Date.now() - 60000 }); const totalRequests = recentRequests.reduce((sum, d) => sum + (d.value || 0), 0); const totalErrors = recentErrors.reduce((sum, d) => sum + (d.value || 0), 0); if (totalRequests === 0) return anomalies; const errorRate = totalErrors / totalRequests; if (errorRate > this.thresholds.errorRate) { anomalies.push({ type: 'error_rate', severity: errorRate > this.thresholds.errorRate * 5 ? 'critical' : 'high', value: errorRate * 100, threshold: this.thresholds.errorRate * 100, timestamp: Date.now(), message: `Error rate ${(errorRate * 100).toFixed(2)}% exceeds threshold ${(this.thresholds.errorRate * 100).toFixed(2)}%` }); } return anomalies; } detectThroughputDegradation() { const anomalies = []; const rate = this.metrics.getRate('requests.total'); if (rate < this.thresholds.throughput.min && rate > 0) { anomalies.push({ type: 'throughput', severity: 'medium', value: rate, threshold: this.thresholds.throughput.min, timestamp: Date.now(), message: `Request rate ${rate.toFixed(2)} req/sec below minimum ${this.thresholds.throughput.min} req/sec` }); } return anomalies; } checkAnomalies() { // Periodically check for anomalies if (Date.now() - this.lastReset > this.windowSize) { this.detectAnomalies(); this.cleanupOldData(); this.lastReset = Date.now(); } } cleanupOldData() { // Keep only recent latencies if (this.latencies.length > 10000) { this.latencies = this.latencies.slice(-5000); } // Clean up old requests (potential memory leak from stuck requests) const now = Date.now(); for (const [id, startTime] of this.requests) { if (now - startTime > 300000) { // 5 minutes this.requests.delete(id); this.emit('request:timeout', { id, duration: now - startTime }); } } } calculatePercentile(values, percentile) { if (values.length === 0) return 0; const sorted = [...values].sort((a, b) => a - b); const index = Math.ceil((percentile / 100) * sorted.length) - 1; return sorted[index] || 0; } getMetrics() { const recentLatencies = this.latencies.slice(-1000); const sortedLatencies = [...recentLatencies].sort((a, b) => a - b); return { requests: { total: this.metrics.getLatest('requests.total')?.value || 0, rate: this.metrics.getRate('requests.total'), active: this.requests.size }, latency: { current: recentLatencies[recentLatencies.length - 1] || 0, p50: this.calculatePercentile(sortedLatencies, 50), p95: this.calculatePercentile(sortedLatencies, 95), p99: this.calculatePercentile(sortedLatencies, 99), avg: recentLatencies.reduce((a, b) => a + b, 0) / (recentLatencies.length || 1), min: Math.min(...recentLatencies) || 0, max: Math.max(...recentLatencies) || 0 }, errors: { total: this.metrics.getLatest('errors.total')?.value || 0, rate: this.calculateErrorRate(), byType: this.getErrorsByType() }, throughput: { current: this.metrics.getRate('throughput.bytes'), avg: this.metrics.getAverage('throughput.bytes'), peak: Math.max(...this.metrics.query({ metric: 'throughput.bytes' }).map(d => d.value)) || 0 }, resources: { cpu: { usage: this.calculateCPUUsage(), system: this.metrics.getLatest('cpu.system')?.value || 0, user: this.metrics.getLatest('cpu.user')?.value || 0 }, memory: { used: this.metrics.getLatest('memory.heapUsed')?.value || 0, rss: this.metrics.getLatest('memory.rss')?.value || 0, heapUsed: this.metrics.getLatest('memory.heapUsed')?.value || 0, heapTotal: this.metrics.getLatest('memory.heapTotal')?.value || 0, external: this.metrics.getLatest('memory.external')?.value || 0 }, connections: { active: this.requests.size, idle: 0, // Would need connection pool integration waiting: 0 } } }; } calculateErrorRate() { const totalRequests = this.metrics.getRate('requests.total'); const totalErrors = this.metrics.getRate('errors.total'); return totalRequests > 0 ? (totalErrors / totalRequests) * 100 : 0; } getErrorsByType() { const errors = {}; const errorData = this.metrics.query({ metric: 'errors.total', startTime: Date.now() - 60000 }); errorData.forEach(point => { const status = point.tags?.status || 'unknown'; errors[status] = (errors[status] || 0) + (point.value || 0); }); return errors; } calculateCPUUsage() { const user = this.metrics.getLatest('cpu.user')?.value || 0; const system = this.metrics.getLatest('cpu.system')?.value || 0; const total = user + system; // Rough approximation of CPU percentage // In production, would use proper CPU usage calculation return Math.min(100, total * 10); } startMetricCollection() { // Collect resource metrics every 5 seconds setInterval(() => { this.recordResourceMetrics(); }, 5000); // Check for anomalies every minute setInterval(() => { this.detectAnomalies(); }, 60000); } reset() { this.metrics.clear(); this.requests.clear(); this.latencies = []; this.anomalies = []; this.lastReset = Date.now(); this.emit('monitor:reset'); } getAnomalies() { return this.anomalies; } } exports.PerformanceMonitor = PerformanceMonitor; //# sourceMappingURL=performance-monitor.js.map